개발자의 오르막
Layer Architecture 에 따른 멀티모듈 구성하기 본문
Layer Architecture 와 멀티모듈
https://catchdream.tistory.com/237
이전 시간에 Layer 계층에 따라 멀티모듈을 구성할 목표를 설정하였다.
- app-service-api
- 프레젠테이션 계층으로서 내부 App API 를 제공한다.
- app-admin-api
- 프레젠테이션 계층으로서 내부 Admin API 를 제공한다.
- app-external-api
- 공공데이터 API 와 통신 후 데이터 후처리 작업을 진행한다.
- app-batch
- Batch 작업을 진행한다.
- domain
- 하나의 모듈은 최대 하나의 인프라스트럭처에 대한 책임만 갖는다.
- 도메인 모듈을 조합한 더 큰단위의 도메인 모듈이 있다.
- common
- Type, Util 등을 정의한다.
우리는 domain 모듈을 공유하여 선택적으로 프레젠테이션 계층과 결합하여 배포를 진행할 것입니다.
이유는 admin-api , service-api 등 처럼 관리자 사이트를 만들던, 앱을 위한 REST API 를 만들던 우리는 같은 도메인에 접근하여 서비스를 제공하기 때문입니다.
각 api 를 레파지토리를 분리하여 개발할 경우, 우리는 도메인의 변경에 따라 2개의 레파지토리에 변경사항을 반영해야 하는 관리포인트의 분산 문제가 있습니다.
멀티모듈을 사용하면 같은 도메인 계층을 import 하여 합쳐진 Jar 파일을 간단하게 뽑을 수 있습니다.
- service-api , admin-api 의 build.gradle
implementation project(":coffee-chat-domain")
구체화
계층 별로 나눈 WAS 의 역할을 좀 더 구체적인 모듈 단위로 나눠보기 위해 이희창님이 강의에서 설명해주신 Layer 별 특징과 역할에 대한 표를 첨부하였다.
Layer 별 특징과 역할
Layer | Description | 주요 객체 |
사용자 인터페이스 (interfaces) |
사용자에게 정보를 보여주고 사용자의 명령을 해석하는 책임 | Controller, Dto Mapper(Converter) |
응용 계층 (application) |
수행할 작업을 정의하고, 표현력 있는 도메인 객체가 문제를 해결하게 한다. 이 계층에서 책임지는 작업은 업무상 중요하거나 다른 시스템의 응용 계층과 상호작용하는 데 필요한 것들이다. 이 계층은 얇게 유지되고, 오직 작업을 조정하고 아래에 위치한 계층에 포함된 도메인 객체의 협력자에게 작업을 위임한다. | Facade |
도메인 계층 (domain) |
업무 개념과 업무 상황에 대한 정보, 업무 규칙을 표현하는 일을 책임진다. 이 계층에서는 업무 상황을 반영하는 상태를 제어하고 사용하며, 그와 같은 상태 저장과 관련된 기술적인 세부사항은 인프라 스트럭처에 위임한다. 이 계층이 업무용 소프트웨어의 핵심이다. | Entity, Service, Command, Store, Executor, Factory(interface) |
인프라 스트럭처 계층 (infrastructure) |
상위 계층을 지원하는 일반화된 기술적 기능을 제공한다. 이러한 기능에는 애플리케이션에 대한 메시지 전송, 도메인 영속화, UI 에 위챗을 그리는 것 등이 있다. | low level 구현체, Spring JPA, RedisConnector, … |
위의 표처럼 계층을 역할에 따라 나누고, 각각의 계층은 인터페이스를 통해 추상화가 기반이 됩니다.
Service-Api Layerd
- Service-Api 는 프레젠테이션 계층에 속합니다.
- Client 에게 요청을 전달받아 그 요청을 도메인 계층에서 사용될 수 있도록 Dto 를 VO 를 Convert 작업을 진행합니다.
- VO 를 해당 도메인 계층으로 넘겨 비즈니스 로직을 수행하게 한 후 응답값을 Dto 로 다시 Convert 작업을 진행하여 Client 에게 내려줍니다.
- 프레젠테이션 계층인 Service-Api 는 Controller , dto , facade 로 구성합니다.
SignUpController.java
@PostMapping("{uuid}/sign-up")
@ResponseBody
public ResponseEntity<ApiResponse> signUp(
@PathVariable(name = "uuid") String uuid,
@RequestBody @Valid SignUpDto.SignUp request) {
MemberCommand.SignUp parentCommand = signUpDtoMapper.of(request);
String signUpToken = memberFacade.signUp(uuid, parentCommand);
SignUpDto.SignUpResponse response = signUpDtoMapper.of(signUpToken);
return new ResponseEntity<>(ApiResponse.OK(response), HttpStatus.CREATED);
}
- 위는 sign-up Controller Api 부분입니다.
- 클라이언트에서 받은 요청은 SignUpDto 로 받습니다.
- 서비스에서 사용될 수 있는 불변객체인 MemberCommand 로 Facade 로 전달합니다.
- Facade 로 부터 받은 데이터를 SignUpDto 의 Response 로 Convert 를 진행 후 응답으로 돌려줍니다.
MemberFacade.java
@Service
@RequiredArgsConstructor
public class MemberFacade {
private final SignUpServiceFactory signUpServiceFactory;
private final MemberService memberService;
private final NotificationService notificationService;
public String signUp(String uuid, MemberCommand.SignUp request) {
SignUpService signUpService = signUpServiceFactory.create(request);
String signUpToken = signUpService.signUp(uuid, request);
notificationService.notifySignUpEmail(uuid);
return signUpToken;
}
}
- facade 에서는 해당 비즈니스 로직을 수행할 service 를 호출합니다.
- 예를 들어, 회원가입을 진행한다고 하였을 때, 회원가입에 대한 비즈니스 로직을 수행하는 서비스와 회원가입 결과를 이메일로 알려줄 서비스를 호출합니다.
- 이는 하나의 트랜잭션 단위가 아닌 로직의 단위로 구성됩니다.
Domain Layerd
- domain 은 비즈니스 로직을 수행하고 Persistence 에 데이터를 요청합니다.
- 앞선 Presentation 계층의 Facade 에서 호출한 순서대로 서비스를 진행합니다.
SignUpService.java
@FunctionalInterface
public interface SignUpService {
String signUp(String uuid, MemberCommand.SignUp signUp);
}
MentorSignUpService.java
@Service
@RequiredArgsConstructor
public class MentorSignUpService implements SignUpService {
private final MemberReader memberReader;
private final MemberStore memberStore;
@Override
public String signUp(String uuid, MemberCommand.SignUp signUpInfo) {
validateSignUp(uuid, signUpInfo.getEmail(), signUpInfo.getNickname());
MentorDetail mentorDetail = MentorDetail.createMentorDetail(signUpInfo.getMentorDetailInfo());
mentorDetail.validateYear(signUpInfo.getMentorDetailInfo().getYear());
Member newMentor = Member.MentorSignUp(uuid, signUpInfo, mentorDetail);
Member savedMentor = memberStore.store(newMentor);
return savedMentor.getUuid();
}
}
- MentorSignUpService 는 SignUpService 의 signUp 을 구현한 서비스이다.
- validate 로직을 수행한 후 Database 에 요청하기 위해 Member Entity 객체를 생성합니다.
- 이후 Read 의 요청과 Write 의 요청에 따라 Reader, Store 인터페이스를 호출합니다.
MemberStoreImpl.java
@Component
@RequiredArgsConstructor
public class MemberStoreImpl implements MemberStore {
private final MemberStoreRepository memberStoreRepository;
@Override
public Member store(Member member) {
return memberStoreRepository.save(member);
}
}
- 도메인 계층으로부터 넘겨받은 Entity 를 기준으로 데이터베이스에 접근하여 Insert 작업을 수행합니다.
- 이후 memberStore 인터페이스를 호출해 데이터베이스에 Insert 작업을 진행하게 한 후 Return 값을 다시 프레젠테이션 계층으로 넘겨줍니다.
'Toy Project > Tortee' 카테고리의 다른 글
SpringBoot 에 Firebase Admin Auth SDK 적용하기 (0) | 2022.12.12 |
---|---|
Spring Boot Rest Docs 멀티모듈 프로젝트에 적용 (0) | 2022.10.17 |
지속 가능한 개발을 위한 멀티모듈과 SpringBoot (0) | 2022.10.10 |
Comments