이채민
카드 컴포넌트 스타일링 재사용 방식 비교
tailwindcss @components vs 스타일링된 컴포넌트 별도 정의 방식 <Card></Card>
1.
@components 는 가독성은 후자에 비해 떨어지지만 태그, 스타일링 등을 확장하기 수월
// global.css
@layer components {
.card { ... }
}
// 활용
<div className="card 덮어쓸_스타일">...</div>
<section className="card">...</section>
JavaScript
복사
2.
컴포넌트 정의시 가독성은 높지만 커스텀을 위해서는 props로 넘겨야한다는 불편함과 다른 태그 사용 안됨.
function Card({className, children}){
// className을 상속받아 추가
return <div className={`스타일 ${className}`}>{children}</div>
}
// 활용
<Card></Card>
JavaScript
복사
→ 단순 배경색 뿐 아니라 커스텀되어야하는 스타일링이 많아 tailwindcss @components 채택
필터링을 가지는 리스트 모두 클라이언트 컴포넌트로 구현해야하는가?
1.
CSR을 최소화하자 - 서버 컴포넌트만을 활용 할 순 없을까?
→ path variable을 활용하여 서버 컴포넌트로 일괄 변경 /funding/{category}
2.
카테고리와 정렬을 모두 가지는 리스트의 경우는 어떻게 처리할 것인가?
모두 path variable로 나타내기에는 URL 설계 방식에 어울리지 않아보임
e.g. payments/{category}/{filter}
→ searchParams를 함께 활용하자 /all?sort=latest
3.
잘못된 params와 searchParams를 판별하는 validation 로직 필요
Layout에서 해당 로직 추가 시도
→ layout.tsx 에서는 params만, page.tsx에서는 searchParams를 validate하기로 함
4.
뒤로가기 클릭시 홈으로 이전 페이지로 이동하길 바라는 사용자의 기대와는 달리 이전에 선택한 카테고리 탭으로 이동하는 문제 발생
영상자료
라우팅 히스토리를 관리하기 위해서는 클라이언트 컴포넌트여야함.
→ 모든 필터가 삽입된 리스트페이지를 클라이언트 컴포넌트로 변경
use client 를 사용하는 것에 죄책감을 갖지 말자!
클라이언트/서버 컴포넌트는 각각의 장점이 존재하며,
이를 적절히 섞어 사용할 수 있는 것이 Next.js의 특징. 잘 활용하도록 하자
커스텀 타입 체크
const fruit = ["apple", "banana", "grape"] as const;
type Fruit = (typeof fruit)[number];
const isFruit = (x: any): x is Fruit => fruit.includes(x);
JavaScript
복사
학습내용
•
타입스크립트 자바스크립트에 타입 시스템을 추가하고, 코드가 트랜스파일될 때 삭제된다.
typeof 의 결과가 저장되는 변수 키워드(let, type)에 따라 사용되는 오퍼레이터가 다름
(즉, 결과값이 다름)
•
as const
array, object와 같은 리터럴 타입을 as const 을 통해 타입의 범위를 구체적으로 제한할 수 있다
const ab = ["a", "b"]; // const ab = string []
const ab = ["a", "b"] as const; // const ab = readonly ["a", "b"]
JavaScript
복사
•
is
사용자 정의 타입인지 확인하는 용도로 사용되는 키워드로,
특정 요소의 타입을 체킹하는 함수의 리턴 타입으로 사용된다
// a의 타입이 string인 경우, 즉 리턴값이 true인 경우 value는 string 타입이라 정의
function isString(value: unknown): value is string {
return typeof a === "string";
}
JavaScript
복사
useForestField 커스텀 훅으로 분리
카드 이미지 방향 설정 util 함수 구현
서승주
MSA: 영향이 가장 작은 것 → 부하가 가장 많은 것 순서대로 쪼갬
1. 결제 시스템(Pay-Transaction-Payment)
결합도
상황: 결제/환불 요청 흐름에서 로직간 결합도가 높았음
문제: 유저가 결제를 요청하면 성공/실패 응답 받기까지 시간 오래 걸림
해결: 트랜잭션 생성 완료되자마자 유저에게 결제 상태 변경(pending) 반환
트랜잭션 성공 → Settlement, Notification, Schedule 서비스는 kafka로 통신
Pay Service가 죽었을 경우?
•
상황: 유저가 결제 요청 → (Transaction Service → …) 로직 진행 중에 Pay Service 죽음
⇒ 유저가 결제 성공/실패 어떻게 알 건지
◦
잘못하면 결제 업체가 돈 먹을 수 있음
⇒ Pay Service가 죽었는지 살아있는지 매번 확인? retryCount?
외부 클라이언트
상황: 내부 결제 시스템만 생각해서 단순히 결제하는 내부 클라이언트가 요청하는 것으로 생각
결제 과정 프론트엔드가 존재하는데 결제 클라이언트와 별개로 생각 X
= (클라이언트) 결제 과정 페이지에서 결제 요청 → (서버) 결제 처리라고 생각
⇒ (외부 클라이언트) 결제 요청 → (결제 과정 클라이언트) 결제 페이지 → (결제 서버) 결제
문제: (외부 클라이언트 → 결제 과정 클라이언트 → 서버) 로직은 보안 위험
최종적으로 결제 완료 후 외부 클라이언트에게 반환했을 때도 외부 클라이언트가 자신의 서버에 결제 안된 것처럼 속일 위험 있음
해결: (외부 클라이언트) 결제 요청 → (외부 서버) 유저인증, 결제 요청
→ (결제 서버) 결제, 리다이렉트 (결제 과정 클라이언트) 결제 성공/실패 응답
실제 결제할 때 한 번 더 확인?
결제 요청을 하고 카드 어플이나 웹사이트로 결제 확인을 하고 다시 결제 요청 화면으로 돌아왔을 때
유저가 체크 표시 O → “진짜” 결제 / X → 결제 취소
⇒ 결제 요청을 한 것 자체는 트랜잭션에 기록 되어야 함(상태 변경: Pending → Succss/Faild/Canceld)
이 때, 유저의 계좌에서 진짜 실제 돈을 빼서 들고 있을까 아님 시스템상으로만 들고 있을까
이 경우 만약 유저가 체크 표시 화면에서 나가서 다른 상품을 결제하면 어떻게 될까?
⇒ 유저가 체크 표시를 하고 최종 버튼을 눌러야 “진짜” 결제가 될 때 실제 돈이 빠져나가야 함
결제 요청 금액을 어떻게 관리하는가
•
은행의 역할까지 수행 → 고객 계좌에 holding된 금액을 별도 관리 → 금액 이상만큼 다른데서 못찾아가게 처리
•
플랫폼의 역할만
◦
진자 결제를 할때 돈을 차감해오는 과정에서 돈 부족하다면 모두 롤백처리
⇒ 최종 결제시에는 무조건 해당 금액이 있는지 체크하고 없다면 롤백
→ 이전: pay service에서 결제 진입 시에만 1차 금액 확인 후 금액이 없다면 결제 X
→ 수정: payment service에서 실제 결제(최종 결제)시에 다시 한 번 해당 금액 확인
상황: 결제 요청하면 바로 알아서 흘러가서 결제되는 형식
문제: 결제할 때 결제 프론트엔드에서 최종적으로 결제 버튼을 눌러야 “진짜” 결제가 됨
즉, 최종 결제 버튼 누르지 않고 나가면 결제 취소
해결: 로직 한 번 더 분리
•
v1/payment 처음 결제 요청 → 트랜잭션 Pending 상태, 결제 프론트엔드로 리다이렉트
•
v1/payment/verify 리다이렉트 url에 있던 토큰으로 상태 확인
•
v1/payment (결제 프론트엔드) 최종 결제 버튼 → 트랜잭션 ID 찾고 실제 결제 로직
•
payment service에서 최종 결제 전에 한 번 더 페이에 돈이 있는지 확인
◦
현재 로직에서는 pay service에 account, pay, point가 있어서 이렇게 그림이 나올 수 밖에 없음
포인트 및 실결제
상황: 포인트 사용, 결제 API가 각각 존재
•
PUT /v1/pay/points/use
•
POST /v1/pay/payment/{sellerId}
클라이언트가 결제 전 포인트 사용 API 호출 → (원래 금액 - 포인트 사용 금액) 결제 API 호출
문제: 클라이언트에서 포인트 사용 API를 호출하지 않고 (원래 금액 - 포인트 사용 금액)을 넣은 결제 API 호출할 수 있음
해결: 포인트 사용은 결제할 때만 사용 → 결제 로직 내부에서 포인트 사용까지 처리
계좌 예외 처리
상황: 계좌 생성, 수정, 삭제 API 존재
문제: 페이는 무조건 한 개 이상의 계좌를 가지고 있어야 함
같은 계좌 번호가 여러 개 등록되는 상황(은행API를 연결하지 않았기 때문)
해결: 페이 생성 시 계좌 생성 로직 재활용하기
커스텀 예외 처리 활용하기: 같은 계좌 번호 사용 X 계좌가 1개일 때 삭제 X
취소 및 환불
한 서비스 내에 로직이 많아지는 것 vs. 통신이 많아지는 것 → 어떤 게 효율적?
각 서비스가 어디서부터 어디까지 담당할 것인가 명확하게 표현되어야 함
상황: payment service와 refund service 분리
•
payment service: 결제
•
refund service: 취소와 환불
해결: http 통신이 적어서 더 효율적이라고 판단 → payment service와 refund service 통합
2. Acitivity History
Admin Service에는 관리자(manger, admin)의 행동 히스토리 작성 (OpenFeign 사용)
상황: Item/Funding Service에서 Admin Service(ActivityHistory) 호출해서 작성
→ v1/items, v1/fundings
문제: Admin Service가 죽으면 Item 서비스에도 영향 O
해결: Admin Service에서 Item Service 호출
→ Admin Service가 과부화될 위험 있음 But, 죽어도 다른 서비스에 영향 X
→ URI가 v1/admin/으로 시작하게 할 수 있음
•
AdminItemController: v1/admin/items
•
AdminFundingController: v1/admin/fundings
3. 관리자
상황: 관리자와 유저의 URI가 같고 안에 서비스 로직에서 권한 확인 후 각각의 결과 반환
문제: 관리자와 유저는 반환 값이 다름
해결: 유저는 Page, 관리자는 List로 반환, URI 분리
•
단, 유저는 상황마다 다름
◦
펀딩 리스트는 정보량이 많아 페이지네이션 적용하기
◦
아이템이나 기부사 리스트는 리스트로 반환
Before
After
4. 코드 분리
서비스 내부에서 작동하는 코드 → Internal 인터페이스로 분리
ex. public class ItemServiceImpl Implements ItemService, InternalService {...}
5. 아이템 반환 with 채민
상황: MyItem은 UPDATE가 많아 MyItem 중 현재 사용 중인 아이템은 UsingItem 테이블로 분리(일대일 관계)
사용 중 아이템 한 개마다 서버를 호출하는 구조
•
POST /v1/items/my-items/using-items, PUT DELETE /v1/items/my-items/using-items/{usingItemId}
문제: 서버 요청이 빈번한 상황 → 서버 부하 문제
고민: 새로운 API 만들어서 → action으로 CREATE, UPDATE, DELETE 넣어 보내기
•
/v1/items/my-items/using-items/action
문제: 서버가 action에 다른 값 들어오는 상황을 처리해야 함
고민: 편집 모드를 넣기
편집 버튼 클릭 → 아이템 수정 → 완료 버튼 클릭 + 리스트로 보내기
문제: 서버 부하는 적어지나 사용자가 바로바로 아이템을 수정할 수 없어 유저 친화적 X
해결: GraphQL 도입
•
REST보다 네트워크 트래픽 감소
•
하나의 mutation → 단일 엔드포인트 ⇒ 복잡한 작업(CREATE, UPDATE, DELETE) 처리
•
subscription 기능 사용 → 실시간으로 UI에 반영
6. 채팅방
상황: 채팅 AI 모델 SpringBoot 서버(WebSocket) 클라이언트
→ 실시간성, 다중 사용자 동시 처리 및 성능이 중요함
문제
1.
동기적 I/O 사용하는 Tomcat으로 설계 → 동시 요청 처리량 제한 ⇒ 성능 저하
2.
비효율적인 비동기 처리
•
Thread.sleep(500): 메시지 응답 생성에서 불필요한 블로킹 호출
•
CompletableFuture.runAsync
: 별도의 스레드풀 X or 최적화 X → 요청량 ↑ ⇒ 병목 현상
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500); // 불필요한 블로킹 호출
ChatMessage aiResponse = ChatMessage.builder()
.sender("AI")
.message(chatService.generateResponse(chatMessage.getMessage()))
.type(MessageType.CHAT)
.build();
messagingTemplate.convertAndSend("/topic/public", aiResponse);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Java
복사
3.
AI 응답 생성 병목
•
RestTemplate 사용해 동기적으로 처리 → 호출량 ↑ ⇒ 병목 현상
@Service
@RequiredArgsConstructor
public class ChatServiceImpl implements ChatService {
private final RestTemplate restTemplate;
@Value("${fastapi.url}")
private String FASTAPI_URL;
@Override
public String generateResponse(String userMessage) {
try {
Map<String, String> requestPayload = new HashMap<>();
requestPayload.put("question", userMessage);
HttpEntity<Map<String, String>> requestEntity = new HttpEntity<>(requestPayload);
ResponseEntity<Map> responseEntity = restTemplate.postForEntity(FASTAPI_URL, requestEntity, Map.class);
if (responseEntity.getStatusCode() == HttpStatus.OK && responseEntity.getBody() != null) {
Map<String, Object> responseBody = responseEntity.getBody();
return responseBody.get("answer").toString();
} else {
return "서버로부터 유효하지 않은 응답";
}
} catch (Exception e) {
return extractDetailFromErrorMessage(e.getMessage());
}
}
}
Java
복사
해결
•
WebFlux + Netty 사용
◦
WebFlux: 비동기 논블로킹 방식
◦
Netty: 적은 스레드로 높은 동시 요청 처리할 수 있음
•
AI 응답에 gRPC 사용
◦
네트워크 오버헤드 줄이고 요청-응답 시간 단축
▪
HTTP/2
◦
Protocol Buffers → 데이터 직렬화 최적화
▪
JSON/텍스트 기반 프로토콜보다 데이터 크기 작고 전송 속도 빠름
◦
Kafka도 비동기
▪
메시지 브로커 거쳐 메시지 전달 → 지연 발생
▪
이벤트 기반 동작 → 소비자 지속적으로 polling하는 구조 추가로 구현
▪
⇒ 수십만 명 사용자 동시접속하는 대규모 채팅 애플리케이션에서 확장성 위해 사용 O
최호윤
추천 시스템 응답시간 최적화
•
추천 시스템에서 request를 받았을 때, 응답 시간이 6~7초 소요되는 문제 발생
◦
문제 원인 → 추천 응답을 받을 때 마다 tf-idf 로 벡터라이징 하는 과정을 반복하고 있었음
◦
해결 방안 → HuggingFace 임베딩 모델로 혜택 및 카테고리를 미리 임베딩해서 저장해서 사용
•
추천된 카드 결과에 llm을 사용해서 필요한 키워드를 추출
◦
문제 원인 → 결과를 추출하고 굳이 formatting 함수를 한 번 더 거쳐서 결과를 출력하는 것은 비효율적
◦
해결 방안 → Prompt Engineering을 통해 해결 가능한 부분, 이외에도 Prompting으로 해결할 수 있는 부분은 최대한 Prompting으로 해결할 수 있는 방법을 찾아서 해결할 것
•
결과: 변경 이후 응답 시간을 3~4초로 줄일 수 있었음
챗봇 수정
•
기존에 구현했던 VectorDB를 사용하여 RAG 구성한 방식이 구현하려는 서비스에서 챗봇의 역할과 데이터를 보았을 때 적합하지 않다는 평가.
•
제안 받은 개선 방향
◦
Text-to-SQL 개념을 적용 → 입력 받은 사용자의 질문을 SQL로 변환
▪
프롬프트에 질문에 대한 View를 생성하도록 DDL을 정의
◦
백엔드로 api를 호출할지, DB를 직접 호출할지
▪
호출해서 조회한 내용을 토대로 답변을 생성
•
답변 예시가 다채롭게 있어야, 다양한 View를 만들 수 있음
1. Text-to-SQL 기반 View 생성 흐름
단계별 설명:
1.
View 생성
•
Data Warehouse에서 챗봇에 사용할 데이터의 요소들을 뽑아 View를 생성
2.
사용자 질문 입력
•
사용자의 자연어 질문을 입력받음
3.
Text-to-SQL 변환
•
질문을 기반으로 적절한 SQL 쿼리를 생성
•
생성된 SQL은 데이터베이스의 View 조회 DDL 형태로 변환
4.
질문에 대한 답변 생성
•
생성된 View를 조회하여 데이터를 기반으로 답변 생성
•
예: SQL 결과를 요약하거나 자연어 형태로 포맷
2. API 호출 vs DB 직접 호출
비교
•
API 호출
◦
장점: DB와의 분리 유지, API 게이트웨이를 통한 보안 및 로깅 강화
◦
단점: API의 추가적인 호출 비용과 지연 가능성
◦
적용: 외부 데이터 시스템이나 통합 서비스에서 주로 사용
•
DB 직접 호출
◦
장점: 실시간 응답성 우수, 추가적인 중간 계층 없이 간결
◦
단점: 직접적인 DB 접근이 보안적으로 취약할 수 있음
◦
적용: 사내 전용 시스템이나 직접 접근이 필요한 고성능 요구 환경