2021.12.02
API 설계
- user 도메인에 속하는 기능에서 "/<도메인명>/login" VS "/login" 중 어떤 것이 나을까요?
- login logout처럼 단순한 건 login logout으로 사용하자
- PUT VS PATCH 도메인의 낱개 정보를 변경할 때 어떤 메소드를 사용하나요?(예 : user의 비밀번호만 변경하는 api가 있을떄)
- 엄청 애매하다. HTTP METHOD에 너무 국한되지 않아요 한다. 상황마다 다르다.
- GET을 제외하고는 약간씩 애매하다.
- 일정 공유(게시글 조회)
- 현재는 '여행 자랑 게시글'은
/posts
쪽으로, 동행 맺기 쪽으로는/connection
으로 도메인이 나뉘어 있습니다.(도메인이 entity로 분리되어야 한다고 생각했습니다.) - 이것을
/post
하위로 합쳐서/posts/schedules
,/posts/connections
로 만드는 것은 어떤가요?(도메인을 view 단위로 나누어야 된다고 생각했습니다.) - → Post 도메인을 분리하여
/posts/schedules
,/posts/connections
로 작성하자.
- 회원 정보 데이터
- 좋아요를 누른 게시글 조회, 친구 요청 등을 할 때 클라이언트에서 현재 로그인 하고 있는 유저의 정보를 계속 받아오는 것이 좋을까요? 아니면 (클라이언트 측에서 정보를 보내지 않고) authentication 정보를 읽어서 이것을 사용해야 할까요?
- 현재 로그인한 회원의 정보는 알 수 있으므로 클라이언트에서 서버로 보낼 필요가 없다.
- 일정(체크리스트 체크/해제)
- 요청이 올 때 마다 토글 → POST 요청에 변경하려는 값 포함 → PATCH 일 것 같은데, 어떤 메서드를 사용하는 것이 더 일반적인가요?
도메인 분석 및 명칭
- 현재 8개의 도메인으로 분리되어 있는데, 저희 기준으로 도메인 나눈 것에 대해서 평가 부탁드립니다.
- scheduleshare 나 connectionpost 도메인 명칭에 대해서 어떻게 생각하시나요?
채팅 구현
- 채팅 구현의 난이도가 궁금합니다.
- 소켓, 실시간 어렵다
- 구현을 한다면 채팅 방을 만들고 2명이서 댓글을 다는 방식으로 (정석은 아닌 것 같지만) 만들어도 되나요?
- 이왕 할거면 소켓을 써야 공부도 되고 좋다. (다음기회에)
뱃지의 이벤트와 api 설계
- 뱃지의 경우 클라이언트로부터 요청이 아닌 조건을 만족하는 이벤트 형식인데, 이 경우 api 설계는 어떻게 해야 하나요?
- 모든 응답값에 badges [] 를 보내주면 된다.
2021.12.09
생성, 수정, 삭제에 대한 HTTP 응답
ex) A라는 내용에서 B로 수정을 했을때, 서버에서 B라는 내용을 리턴해줄 필요가 있을까요? 아니면 단순히 성공, 실패 여부만 HttpStatus로 보내줘도 될까요?
정답이 없다. 닭이 먼저냐 달걀이 먼저냐
도메인과 API에 대한 질문 (여행과 하위 엔티티 관련)
- 메모, 투표, 체크리스트들의 리포지토리를 만든다면 각각의 컨트롤러, 서비스도 만드는 게 맞을까요?
* 최대한 나누는게 좋다 (SRP)
* Repository를 따로 만드는 것과 서비스, 컨트롤러를 따로 만드는 것은 다른 관점이다.
게시글 uri 관련 질문
- 기존에 게시글 이라는 상위 도메인이 있고, 그 하위에 동행 게시글과 여행 공유 게시글을 두는 형태로 테이블을 구성했었습니다.
- (postId를 통해 게시글에 접근하고, 해당 레코드에서 가지고 있는 여행 게시글 id나 동행 게시글 id를 통해 내용을 불러오는 형태)
- 그래서 게시글 관련 uri를
/posts/{postId}/schedules
,/posts/{postId}/connection
과 같은 형태로 사용했었습니다.
기존 형태

- 현재는 동행 관련된 부분은 일정상의 문제로 모두 후순위로 밀린 상태라 게시글 도메인 측에 남아있는 우선순위가 높은 도메인은 '여행 공유(자랑) 게시글' 및 이에 대한 좋아요와 댓글입니다.
- 향후 동행 관련 도메인이 추가가 될 것이라는 가정 하에, 이 게시글 관련 uri를 어떻게 설정하면 좋을지가 고민입니다.
- 1안 - 현재 :
/posts/{postId}/schedules
,/posts/{postId}/connection
- 2안 -
/posts/schedules/{schedulePostId}
,/posts/connection/{connectionPostId}
- 3안 - 다른 형태를 고려한다...
- /schedules/1 ← post x /schedules/posts/1 /schedules/posts/1/liked /schedules/posts/comments/1 /connection/1 ← connection x /connection/posts/1 /connection/posts/1/liked /connection/posts/comments/1
- /posts/schedules/1 /posts/schedules/1/liked /posts/schedules/comments/1 /posts/connection/1 /posts/connection/1/liked /posts/connection/comments/1
현재 형태

* 프론트와 협의
* 도메인간의 선후관계를 따져서 결정하는 것이 절대적인 것은 아니다.
코드 규칙 통일을 어느정도 레벨로 해야 하나요? 취향인가? 규칙인가?
- dto에서 롬복을 쓴다 쓰지 않는다?
- 엔티티 → dto로 변환할때, 컨버터로 사용할지 dto에서 팩토리 메소드를 사용할지
- 생성자로 AllArguments 를 사용할지, 빌더를 쓸지
- 한 도메인의 서비스에서 다른 도메인의 엔티티가 필요할 때, 서비스에 요청을 해서 얻어올 지, repository에 요청을 해서 얻어올 지
- 생성, 수정, 삭제에 대한 HTTP 응답
必 규칙을 정해서 따라야 한다.
유지보수 관점에서도 규칙이 정해져야 더 원할하게 할 수 있다.
게시글 조회(검색) 방식 질문
여행 공유 게시글 조회 페이지 이미지

- 현재 여행 공유 게시글 페이지의 동작 순서 및 방법(?)은 아래와 같습니다.
- 인기 플랜 버튼을 클릭(탭) 한다 → 여행 공유 게시글 10개 정보를 요청한다. 정렬은 default 최신순.
- 검색 조건 및 정렬 조건을 지정하여 검색한다.
- 검색어
- 없으면 다른 조건만 가지고 검색한다.
- 있으면 제목 또는 본문에 해당 검색어를 포함하는지 검색한다.
- 도시
- 특정 도시에 대한 여행 자랑 게시글만 조회한다.
- 전체 도시에 대한 여행 자랑 게시글을 조회한다.
- 테마
- 특정 테마에 대한 여행 자랑 게시글을 조회한다.
- 모든 테마에 대한 여행 자랑 게시글을 조회한다.
- 정렬조건
- 기본은 최신순이다.
- 조회수, 좋아요, 댓글 수 순서로 변경할 수 있다.
- 이런상황에서 조건이 있거나, 없거나에 따라 쿼리에 where 절을 붙이거나 떼거나 하느 방식으로 구현을 하려니 너무 분기가 많이 발생합니다.
- 그래서 현재 두 가지 방안을 고민중입니다.
- 도시를 여행 공유 게시글에 하나의 컬럼으로 두지 않고 다른 테이블로 뺀다. 그리고 모든 여행 게시글에 연관된 도시 리스트(테이블)에 기본적으로 "전체"를 추가해서 조회한다.
- 프론트에서 city를 전달받는다 (전체, 서울, 부산 등...) →
schedulePost.getCityList().contains(city)
처럼 조회를 시도한다. - 여행 일정 (schedule)에 연관된 여행 테마 리스트(테이블)에 모두 "전체"를 추가하고 위와 같은 방식으로 조회한다.
- 일단은 분기가 지저분하더라도 if.. if.. if... if.. 해서 작성한 후 추후에 동적 쿼리에 대해서 조사한 후 적용해본다. 대략 한 16개정도 나올 것 같습니다..
- 어떤 방식을 선택하는 것이 더 나을지 조언주시면 감사하겠습니다.
테스트 코드에서 확인을 할 때, 모든 항목을 다 확인해야 할까요?
- 예를 들어, 서비스 요청에 대한 response 필드 값이 10개라고 하면 전부 다 assertThat 으로 확인해야 하나요?
FM은 다 해야된다.
10개의 필드 중 하나의 값이 없을 수도 있고, 타입이 다르게 올 수도 있다.
버겁지만 전부 다 하는 게 안정적이다.
메인 코드에서 객체 지향이나 클린코드 같은 것은 리뷰도 많이 받았는데, 테스트 코드 파트에서는 어려움이 있습니다.
- 단건 추가 같은 기능이 아닌 목록 조회는 테스트 전에 이미 여러 개가 추가된 상태여야 하고, 투표의 상세 내용 조회도 조회하기 전에 여러 명이 투표한 상태여야 유의미한 조회가 가능할 것 같은데 이런 부분은 어떻게 해야하는지 잘 모르겠습니다. In-memory db가 아닌 mysql 같은 곳에 따로 테스트용 db를 만들어놔야 할까요?
2021.12.
스웨거와 시큐리티
- 저희가 지금 스웨거를 만들긴 했는데 스프링 시큐리티 부분이 아직 미흡해서 제대로 동작 안 하는 부분이 꽤 있습니다.
- 스웨거를 만들 때 시큐리티를 완전 제외하고 도메인 기능을 검증할 수 있는 버전과 시큐리티가 완전히 적용되서 검증하는 버전 두 개로 나누고 진행해야 할까요???
삭제 로직 (멱등성 관련해서 수정에도 동일한 질문)
@Transactional public void deleteSchedule(Long scheduleId, Long memberId) { scheduleRepository.findById(scheduleId) .filter(schedule -> isOwner(schedule, memberId)) .ifPresent(scheduleRepository::delete); }
- 우선 저는 일정 삭제 기능 로직을 위와 같이 작성했습니다.
- 일정을 가져온 다음 -> filter로 일정의 주인 id랑 파라미터 memberId를 비교. 주인이 맞으면 ifPresent() 구문에서 삭제를 합니다.문제는 이러면 일정이 없어도, 멤버의 주인이 아니어도 무조건 성공으로만 응답하게 됩니다. 물론 일정의 주인이 아니면 삭제는 안 되겠지만요..그래서 단계를 나누면
- 일정이 존재하는지 아닌지 판단한다. 없으면 커스텀 예외 throw
- 일정의 주인이 일치하는지 판단한다. 아니라면 커스텀 예외 throw
- 삭제 됐으면 성공 return
이런식으로 작성해야 하나요??
구체적으로 지금 HTTP 메소드 멱등성 때문에 이런 고민을 하고 있습니다.멱등성은 한 번 요청을 하든 여러 번 요청을 하든 같은 결과를 가져야 한다고 알고 있습니다.
제 코드와 아래 1,2,3 로직 모두 한 번 요청을 하든 여러 번 요청을 하든 결과는 같아서 멱등성을 유지한다고 생각합니다.
근데, 결과값이 같다는 게 리턴값도 같아야 하는건지 궁금합니다.제 코드에서는 한 번이든 여러 번이든 성공 응답만 가지만,아래 1,2,3 로직에서는 처음 유효한 요청에 대해서는 성공이지만, 2번째부터는 존재하지 않는 리소스에 대한 삭제 요청이므로 Exception이 발생합니다. 어떤 방식이 맞는건지 궁금합니다.
JPA vs nativeQuery (JOIN 관련)
JPA를 써서 쓸데없는 join 쿼리가 나간다면 nativeQuery로 변경해주는 게 좋은거겠죠???
아래 코드는 특정 일정에 속한 모든 투표 리스트를 받아오는 메소드입니다.
@Query(value = "SELECT * FROM voting WHERE schedule_id = :scheduleId", nativeQuery = true) List<Voting> findByScheduleId(Long scheduleId);
위 코드는 JPA의 Query Method 이기 때문에 @Query 부분이 없어도 기능은 정확히 수행됩니다.근데, 아래 쿼리 메소드만 있으면 쿼리를 봤을 때
left outer join schedule schedule1_ on voting0_.schedule_id=schedule1_.id
와 같이 조인 쿼리가 나갑니다.근데, @Query 구문에 nativeQuery로는 JOIN 없이 할 수 있습니다. JOIN을 줄일 수 있다면 최대한 줄이는 게 좋은가요??
테스트 방식에 관한 질문
List<MemoSimpleResponse> memos = scheduleService.getMemos(schedule, MEMBER_ID); List<String> titles = memos.stream() .map(MemoSimpleResponse::getTitle) .collect(Collectors.toList()); List<String> contents = memos.stream() .map((MemoSimpleResponse::getContent)) .collect(Collectors.toList()); List<Long> ids = memos.stream() .map((MemoSimpleResponse::getId)) .collect(Collectors.toList()); // Then assertThat(ids).containsExactly(memo1, memo2, memo3); assertThat(titles).containsExactlyInAnyOrder("memotitle1", "memotitle2", "memotitle3"); assertThat(contents).containsExactlyInAnyOrder("content1", "content2", "content3");
테스트를 할 때,저는 memos 에서 id만 따로 추출 후 테스트,content만 따로 추출 후 테스트,Title만 따로 추출 후 테스트이렇게 진행했습니다. 위와 같은 방법 말고 예를 들어
assertThat(memoResponse).isEqualTo(new MemoResponse(1,"memotitle1","content1"));
이런 식으로 MemoResponse의 hashCode(), equals() 메소드를 재정의 하고 테스트를 진행하는 게 좋을까요???아니면 타입 검증은 못하겠지만 toString() 재정의로 비교해도 괜찮을까요??
제약 조건을 객체에서 검증하는데 DB의 DDL에도 제한을 걸어놔야 할까요?
예를 들어, 제목의 길이가 10자 미만이라고 한다면 자바 코드에서도 검증하고,
DDL 에서 VARCHAR(10) 이렇게 제한 해놔야 하나요?
만약 제목의 길이는 10자로 제한해놨다가 요구사항이 바뀌어서 제목이 20자까지 가능. 이러면 어떻게 해야 하나요???
VARCHAR(10)과 VARCHAR(100)은 차이가 크다. DB는 효율적이어야 한다. 애플리케이션과 DB 모두에서 체크하자.
DB는 데이터를 관리하는 관점, 애플리케이션은 들어갈 데이터를 관리한다
에러의 일관된 응답 형식을 만들어야 할까요?
성공 응답은 요청 별로 Response 를 만들었었습니다.
근데 예외는 대부분이 메시지만 응답해주는 형식이고 몇 몇 예외만 추가적인 데이터를 보내는 경우가 상황입니다.
예외는 ErrorResponse 같이 따로 일관된 형식을 만들어 주는게 좋을까요?
잭슨 직렬화, simple과 detail은 합치자 (필드가 보이고 안 보이고의 차이라면)
ResponseEntity<String> 일때와 ResponseEntity<ErrorResponse>
- String으로 응답할 때는 ResponseHeader의 ContentType이 plain/text charset=ISO?? 로 됩니다.
- 근데, ErrorResponse 클래스를 만든 다음에, message 필드로 응답할 때는 json charset=UTF-8로 인코딩이 잘 됩니다.
상위 엔티티 검증
일정 도메인의 대부분의 URI는
"/{scheduleId}/memos/{memoId}"
와 같습니다.메모든 체크리스트든 투표든 전부 scheduleId를 타고 들어가는 상황이라면, 서비스 코드에서 항상 scheduleId 에 대응하는 schedule이 존재하는지 검증하는 로직이 들어가야 하나요??
비슷한 질문으로,
findScheduleById(scheduleId); validateScheduleMember(scheduleId, memberId);
위의 코드는 schedule이 있는지 검증하는 부분이고
아래 코드는 member가 schedule에 접근 권한이 있는지 확인하는 코드입니다.
사실 Schedule이 없이 ScheduleMember는 존재할 수 없으니까 아래 코드만 있어도 위의 코드도 자동으로 검증이 된다고 생각합니다. 그렇다면 위의 코드는 없어도 괜찮을까요??
Schedule이 없이 ScheduleMember는 존재할 수 없으니까 한 번 만 검증 해도 된다.
enum으로 리턴? String으로 리턴
Response에서 enum 타입이나 LocalDate 타입 같은 걸로 프론트에 응답해도 괜찮을까요?
웬만하면 String으로 바꿔서 보내주는 게 좋을까요?
결국
파싱
의 문제로 보이는데요 raw 데이터를 줘서 그쪽에서 파싱하느냐, 아니면 서버가 파싱해서 주면 FE는 그대로 출력만 하면 되느냐 의 문제로 보입니당. 즉, FE/BE 서로 타협하면 결정됨.회원 id의 보안처리?
게시글 목록 조회를 하면 게시글의 id, 제목 리스트를 반환합니다.
근데, 회원의 친구 목록을 조회할 때도 회원의 id 리스트를 프론트에 반환해도 괜찮은 건가요??
게시글 같은 도메인의 id랑 다르게 뭔가 암호화처럼 보안적인 요소가 필요하지 않을까? 란 생각이 들었습니다
깊게 들어가면 회원의 고유 번호(id?)만을 가지고 BE에서 제공하는 API를 조작할수 없게 하면 되긴 하는데요. 지금은 규모가 작은 (+크리티컬하지 않는) 서비스라 깊게 고민할것까진 없어보이지만 특정 서비스의 request 만 보면 대부분 데이터를 유추해서 크롤링 하듯 일부 허술한 서비스도 많긴 해요 ㅎ
그 데이터를 노출하는데 크리티컬 하느냐의 관점으로 생각됩니당, 크리티컬 하면 api 나 응답에 어떻게든 노출되면 안되겠죠.
저희 AWS가 매일 죽어요 ㅜㅜ 제대로 한 건지 검토 부탁드립니다
로깅 전략
누가 어떤 요청을 했는지, 혹은 추후에 디버깅 목적으로써 필요한 정보들을 남기는게 일반적입니다.
로그는 최대한 많이 남기고 불필요한 로그를 제외하는 식의 리팩토링을 하는게 좋아보이더라구요
로그인 암호화 관련
로그인 시 비밀번호 데이터를 평문으로 보내야 하나요? 아니면 암호화해서 보내야 하나요?
당연히 암호화해서 보내야 한다. 서버에서는 비문을 그대로 써도 되고 복호화해서 써도 된다.
토큰 만료 시간
토큰이 만료되기까지의 시간을 산정하는 방식으로 진행하셨는지 궁금합니다.
토큰 만료 시간 등 비즈니스 모델에 따라간다.
로그 남길 때 포인트 컷 패키지 구조
- 도메인 별로 포인트 컷을 나눴는데 더 편한 방법이 있을까요?
이미지 관련 작업은 도메인이 독립적으로 분리되는 게 맞을까요
현재는 회원 프로필만 이미지 관련 작업이 있는데 Member 하위로 들어가는 게 맞을까요? 아니면 여러 도메인에도 쓰일 수 있는 여지가 있으니 분리하는 게 맞을까요? (패키지 및 컨트롤러, 리포지퇼 등등) 멤버 하위로 넣자
문자열 검색 알고리즘 (그냥 궁금중)
알고리즘 문제에서는 KMP 같은 거 있었는데요. 현재 저희는 QueryDSL의 contains만 사용한 상태입니다.
전부 꺼낸 다음에 어플리케이션 레이어에서 알고리즘 적용하는건 말도 안 되는 것 같고
DB에 저장된 게시글에서 검색어와 일치하는 내용을 검색하려면 DB내에 검색 알고리즘을 수행하는 function을 만들어 놔야 하나요??
CQRS란 패턴도 있다.
검색을 하려면 쿼리로 해결하지 말고 캐시를 활용하자
색인 테이블이나 Elastic Search같은 색인 서버를 활용해보자
로그 메시지 관리를 담당하는 클래스가 필요한가요?
응답용 메시지는 exception.properties에서 메시지를 관리하고, 자바 코드에서는 ExceptionMessageUtils 클래스를 만들었습니다.
그럼 ExceptionMessageUtils.getMessage("") 같은 코드는 CustomException에서만 사용해야 하나요? 서비스 클래스에서 사용해도 되나요??
같은 Exception이라면 메시지가 무조건 똑같아야 할까요?
@ExceptionHandler에서 캐치하는 exception의 message에는 로그 메시지가 담겨 있어야 하나요? 사용자에게 응답할 메시지가 담겨 있어야 하나요??
- MySQL 대신에 NoSQL을 써보자.
- 인프라가 너무 늦게 구축됐다.
- 데브옵스
- CI/CD
- 시스템 관리
- APM 도구를 활용해서 현재 서비스의 상태를 모니터링 하는 것
- GC, 트래픽, 응답 속도 등
- 튜닝 포인트를 확인할 수 있다
- 병목 구간을 확인할 수 있다
- ( 액추에이터 )다른 포트로 해서 현재 스프링 부트의 상태를 알 수 있다
- 로그 파일을 어떻게 관리할 지
- 오래된 파일은 삭제한다.
- 오래된 파일들은 압축해서 따로 관리한다.
- 에러가 났을 때 서버에 들어가서 볼 건지, 다른 방법으로 훨씬 편하게 볼건지
- 코드 커버리지
- 테스트 코드를 얼마나 잘 짰나?
- 캐시 전략
- 로컬? 리모트? 어떤 캐시를 쓸거야?
- WAS는 몇 대 띄울지? 앞에서 로드밸런서로 관리
- failover