2차 프로젝트 메모

이형석·2024년 9월 24일

2차 프로젝트 정보 (노션)


프로젝트 컨벤션

지속 성장가능한 소프트웨어를 만들어가는 방법
모듈은 이미 만들어져있으니 상관없고, 비즈니스로직과 레이어에 관해서 적용해보기

멀티 모듈

멀티모듈 개념 : https://jojoldu.tistory.com/123

각 모듈 역할

api모듈
: controller
domain모듈
: entity - repository - service
infra모듈
: 외부 기술 (redis, ...)
common모듈
: exception 처리등

각 모듈 의존관계설정

  • 전체 공통 의존관계는 최상위 build.gradle에 설정
  • domain모듈에 필요한 의존관계(JPA)는 domain모듈의 build.gradle에 설정, 다른 모듈이 domain모듈 사용시 domain모듈의 의존관계를 함께 주입받음

빌드는 전체 프로젝트 단위로 빌드

프로젝트 실행

: api모듈에서 실행 (runnable한 모듈)

질문

Q. domain모듈이 가장 독립적인 모듈이며 다른 모듈들이 domain 모듈을 사용해야함.
근데 redis를 이용한 서비스 로직은 domain모듈의 서비스에서 사용하는데,
그럼 domain모듈이 infra모듈을 의존하는게 아닌가?

A. 도메인모듈에서 필요한 것은 캐싱용 레포지토리임. 도메인 모듈에서 캐싱용 레포지토리를 인터페이스로 만듦. 그리고 infra모듈에서 그 인터페이스를 구현함. 그러면 도메인모듈에서는 해당 인터페이스를 사용하기만 하면 됨.(주입받아서)

Github 포크

포크는 보통 협업보다 오픈소스 기여할때 사용하는 방법
-> 나중에 완성하고 포크뜨기
(Fork하여 작업하는 경우, Issue에서 branch생성 후 팝업창 명령어 복붙하는 방법을 어떻게 적용해야 하는지 질문하여 나온 결론)

PR 수정 반영하기

(동일 브랜치에서) commit ammend 해야 하는줄 알았는데, 그냥 commit push해도 반영됨

메인 브랜치에 merge가 생길때마다, 계속 rebase 받으면서 작업하기

rebase전에 반드시 stash!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

PR 후에 Merge받기 전 까지 다른 작업하는 방법

RAG 및 Spring AI

[BE1]이형석
오전 1:38
RAG와 스프링 AI에 대해서도 간단하게 찾아봤습니다
https://modulabs.co.kr/blog/retrieval-augmented-generation/
이 글은 RAG 개념에 대한 설명인데 참고하시면 좋을 것 같습니다
https://blogshine.tistory.com/697
이 글은 스프링 AI를 이용해 RAG서비스를 만들어 본 회고 글입니다.
여기서 VectorDB 라는 걸 이용했다고 해서 ChatGPT 한테 물어봤는데 결론은 이렇다고 합니다.
"유사도 검색을 통해 더 똑똑한 AI 응답을 원한다면 벡터 DB를 사용하는 것이 좋습니다."
"기본적인 FAQ 시스템에서는 벡터 DB 없이도 키워드 매칭이나 규칙 기반 처리로도 충분히 구현할 수 있습니다."

DB 테이블 네이밍

  • 회원 : 팀원들 경험상 다들 User로 사용
  • 단/복수 : MySQL 예약어에 user가 존재하므로 users사용, 따라서 복수형 통일
  • 게시판 : Board, Post, .. 취향차이

DTO에 Record적용

Record

  • DTO같은 불변객체에 사용 (필드에 final 적용됨)
  • setter가 없음 (ModelAttribute로 객체 바인딩 시, 리플렉션 기술로 처리)
  • 생성할 때, 정적 팩토리 메서드 작성하여 이용
  • AllArgsConstructor가 public으로 적용되어 있음 주의

정적 팩토리 메서드

create() : 파라미터 여러개(필드값)
of() : 파라미터 여러개(필드값)
from() : 다른 DTO로 부터 생성(파라미터 1개)
of/create 차이
of: 단순한 객체 생성이나 변환 시 사용되며, 의미적으로 간결하고 직관적인 느낌을 줍니다.
create: 객체를 생성하는 과정에 추가적인 로직이나 검증이 필요할 때 사용하며, 더 복잡한 생성 과정임을 암시합니다.

DTO 네이밍

  • 프론트 - 컨트롤러 : Request/Response
    ex)SignUpRequest
  • 컨트롤러 - 서비스 : Comman, Info
    ex)SignUpCommand
  • 둘 다 DTO에 포함되는 개념
  • 관습적으로 이렇게 사용
  • 코드상으로 Command라는 이름이 직관적이지 않을 수 있음 -> Info 사용
    ex) postUpdater(post, command); //(x)
    ex) postAppender(post, info); //(o)

API 설계

https://jojoldu.tistory.com/783 참고

URL에 필요한 리소스만 명시하기

ex) 게시글에 소속되는 댓글 게시하기
[post] api/v1/post/comment //x
[post] api/v1/comment	//o

IDE 환경변수

application설정파일을 .gitignore에 올리지않고, IDE 환경변수를 이용해 작성

ResponseEntity

  • 반환 데이터가 없을시, ResponseEntity의 제네릭타입 Void로 사용
ex)
@GetMapping
public ResponseEntity<Void> getPostList(){
	return ResponseEntity.success();
}

서비스에서 컴포넌트 사용

서비스에서는 컴포넌트를 이용하여 서비스 흐름만 나타나도록 작성, 자세한 구현은 컴포넌트로 빼서 작성
ex)

@Service
@RequiredArgsConstructor
public class PostService {
	//컴포넌트 주입 (클래스도 주입 가능)
	private final EmergencyRoomRepository emergencyRoomRepository;
	public void init() {
    //서비스 흐름만 알 수 있도록 작성
	//emergencyRoomRepository.saveAll(
	//	emergencyRoomClient.getEmergencyRoomInfoData().stream()
	//		.map(EmergencyRoomInfo::toEmergencyRoom)
	//		.toList());
		emergencyRoomInitializer.init();
	}
}

//자세한 구현은 이 컴포넌트에서 작성
@Component
@RequiredArgsConstructor
public class EmergencyRoomInitializer {
    private final EmergencyRoomClient emergencyRoomClient;
    private final EmergencyRoomRepository emergencyRoomRepository;
    //자세한 로직 구현 메서드
    public void init() {
        emergencyRoomRepository.saveAll(
                emergencyRoomClient.getEmergencyRoomInfoData().stream()
                        .map(EmergencyRoomInfo::toEmergencyRoom)
                        .toList());
    }
}
  • CRUD 컴포넌트 클래스 네이밍 관습
    : Appender, Reader, Updater, Deleter
    ex) PostAppender
  • 컴포넌트 간은 서로 자유롭게 이용가능, 단 순환참조는 불가

CRUD 메서드 네이밍

컨트롤러

HTTP메서드와 동일 (post, get, put, delete)
ex) getPost, putPost, ...

서비스

save/create/add
find
update/modify
remove
ex) savePost, findPost, ..

final 붙이는 습관

  • 엔터티의 List필드에
ex) final List<Post> posts = new LinkedList<>();
  • 생성자 argument에
ex) Post(final String title, final content){
	this.title = title;
    this.content = content;
}

orElseThrow

값이 있으면 해당 값을 반환하고, 그렇지 않으면 예외 제공 함수에서 생성된 예외를 발생시킵니다.
ex)

User user = userRepository.findById(id)
        .orElseThrow(() -> new IllegalArgumentException("user doesn't exist");

참고
orElse : 값이 있으면 해당 값을 반환하고, 그렇지 않으면 다른 값을 반환합니다.
ex)

String name = User.findName().orElse("홍길동");

@GeneratedValue의 strategy

@GeneratedValue(strategy = GenerationType.AUTO)
: Sequence 테이블을 이용하여, id를 몇까지 저장했는지 저장. id생성시 이 테이블을 이용함.

@GeneratedValue(strategy = GenerationType.IDENTITY)
: MySQL의 auto_increment를 이용

따라서 INDENTITY 전략이 더 효율적

실시간 정보 이용 로직

메인화면에서 (가까운 순서로 추린) 병원들에 대해 실시간 정보를 요청(/real-time)
그 때마다 실시간 정보가 fetch됨(공공api호출)(데이터 아예 싹 update됨 + 캐싱)
그 다음 병원을 클릭하여 상세 조회 시 hpId로 조회하여 캐싱정보가 있으면 이용
-> 그러니까, 메인화면에서 병원들 실시간 정보들을 호출해오면서 캐싱하고, 병원 클릭해서 들어가면 캐싱 정보를 사용


PR 리뷰

게시글 기능 구현

게시글 기능 구현 PR

일부 내용

  • Service 인터페이스 삭제
  • service나 component 메서드명에 도메인이름이 또 사용될 필요 없음
    (+Service 메서드명은 서비스로직상 기능하는 이름에 맞도록 작성)
  • Command -> Info
    postAppend하는데 command를 넘겨준다는 것이 코드 상으로 직관적이지 않음
    Appender와 Updater 둘 다 같은 정보를 사용하므로, 이를 나타낼 수 있는 dto를 사용하기
    혹은 dto를 사용하지 않더라도 상관없음, 파라미터를 여러개 두더라도 코드의 직관성에 더 초점을 두기
    * postAppender하는데 command를 넘겨준다는게 이상함
  • Update, Append Info 하나로 묶기
  • findListPageByCity
    findListPageByUser
    => 오버로딩으로 묶기

댓글 기능 구현

댓글 기능 구현 PR

좋아요 기능 구현

좋아요 기능 구현 PR

게시글 파일 추가 기능

게시글 파일 추가 기능 구현 PR

응급실 상세 정보 조회 기능

응급실 상세 정보 조희 기능 구현 PR

게시글 성능 개선

게시글 성능 개선 PR

서비스 테스트 코드 추가

게시글 테스트 작성 PR

SonarQube 기반 리팩토링

SonarQube 기반 리팩토링 PR

profile
금융IT 개발자

0개의 댓글