애플리케이션을 레이어로 나누고 각 레이어에 역할을 정한다.
( 프레젠테이션, 비즈니스, 인프라스트럭처 )
컴포넌트에 맞춰 레이어를 분류하는 것은 폴더를 관리하는 것과 다를 바 없다.
--exe
.game.exe
.pointer.exe
.browser.exe
--image
.game.ico
.game_character.png
...
레이어 구조를 사용한다.
레이어 간 의존 방향은 단방향으로 유지한다.
레이어 간 통신은 인접한 레이어에서만 이뤄지게 한다.
아키텍처란 무엇인가?
정책과 제약 조건(규칙)을 이용해 목적을 달성한다.
(해야 하는 것과 하지 말아야 할 것을 구분 짓는다.)
그러나, 목적에 따라서 제약 조건이 달라질 수 있음을 명심한다.
(Ex. 마감임박이 닥친 경우, 제약조건 다 따지면서 만들 수는 없다.)
레이어 구조를 사용한다.
레이어 간 의존 방향은 단방향으로 유지한다.
문제점과 한계 분석
데이터베이스 테이블을 어떻게 만들지 고민하는 경우.
@Entity
public class AccountJpaEntity{
@Id
@GeneratedValue(...)
private Long id;
private String email;
private String nickname;
}
아래와 같은 구조가 잘못된 레이어드 아키텍처이다.

레이어를 지나치게 추상화해서 각 레이어가 무엇을 설명하고 싶은지 알 수 없다.
다른 한편으로 레이어에 대응하는 스프링 컴포넌트는 또 구체적으로 정해져 있다.
(테이블이 먼저 만들어지지 않은 경우 아무런 개발 시작을 할 수 없다.)
그래서 JPA 엔티티를 우선 개발 = 상향식으로 레이어드 아키텍처 설계
인터페이스 관점에서 어떻게 사용해야 할지를 먼저 설계
(아까 레이어드 아키텍처에서 하향식 접근법)
이 방식은 시스템을 도메인 요구사항 관점에서 보기 시작하는 것을 의미하므로
JPA 엔티티를 우선했던 방식보단 조금 낫다.
(도메인이 무엇인지 파악조차 못한 상태로 진행한 것이다.)
웹소캣용 서버가 될 수도, 원격 호출을 위한 gRPC 서버가 될 수도, 메시지 큐의 컨슈머가 될 수도 있다.
즉 어떤 서버가 될지도 모르는데, 도메인을 무시하고 설계하는 것 자체가 잘못되었다.
또한 요청과 응답 형식을 고민하면 -> 데이터를 고민하고 -> JPA 엔티티 고민해서 -> 트랜잭션 스크립트 코드 탄생
앞선 예제는 JPA나 스프링에 지나치게 의존하는데 이는 JPA나 스프링에 대한 의존성을 빼면 구조가 무너진다.
(프레임워크 의존성을 제거하더라고 구조상 문제가 없어야 하며, 다른 프레임워크 이식을 하더라도 문제가 없다.)
( 도메인 먼저 개발 )
Presentaion(Controller) -> Business(Service) -> Domain -> Infrastructure(JpaRepository)
패키지명이 common, core = 프로젝트에서 사용하는 공통 코드들을 한 군데에 모아서 관리
(유틸 클래스, 설정을 위한 클래스)
아래와 같이 패키지를 구성할 수 있다.
즉 모든 패키지가 레이어드 아키텍처에서 말하는 레이어에 일대일로 대응해야 하는 것은 아니다.
-presentation
-business
-domain
-infrastructure
-aop
-config
-util
또한 common, core는 순수 자바 코드로 작성이 되어야만 한다.
(즉, 외부 라이브러리에 의존하지 않기 위함이다.)
이 말은 @Entity, @Service 같은 스프링 에너테이션도 없어야만 한다.
추가로, 도메인 레이어의 외부 라이브러리 사용은 엄격하게 통제해서 외부 연동이 필요하면 언제든지 컴포넌트가 교체가 되도록 한다.
(왜냐하면, 결국 비즈니스 레이어도 도메인에 포함되기 때문이다.)
Presentation(Controller) -> Application(Service) -> Domain(Domain)
Application(Service) -> Infrastructure(JpaRepository)
비즈니스 레이어 = 애플리케이션 레이어 + 도메인 레이어
이로써 애플리케이션 레이어는 도메인에 있는 코드를 실행하는 역할만 담당한다.
Ex)
@Builder
@RequiredArgsConstructor
public class Account{
public final Long id;
public final String email;
public final String nickname;
public Account withNickname(String nickname){ ... }
}
(항상 도메인 레이어 설계부터 고민.)
따라서, 먼저 도메인 객체들을 만들고 -> 나중에 JPA나 스프링부트를 얹는다.
JPA 엔티티는 아래와 같이 써먹는다.
@Entity
public class AccountJpaEntity{
fields ...
public static AccountJpaEntity from(Account account){...}
public Account toModel(){...}
}
그래서 코드를 다음과 같이 쓸 수 있다.
@Service
public class AccountService{
@Transactional
public Account updateNicknameById(long id,String nickname){
// 영속성 객체 -> 도메인 객체로 변환
Account account = accountJpaRepository.findById(id).toModel();
// 도메인 객체에 작업을 위임
account = account.withNickname(nickname);
// 도메인 객체 -> 영속성 객체
accountJpaRepository.save(AccountJpaEntity.from(account));
}
}
(JPA 라이브러리. 스프링 라이브러리를 임포트 하면 안됨!)
아직까지 JPA에 의존적이다.
그래서 의존성 역전을 사용한다.(인터페이스를 사용하자.)
@Service
public class AccountService{
private final AccountRepository accountRepository;
...
}
public interface AccountRepository{
public Account findById(long id);
public void save(Account account);
}
@Repository
public class AccountRepositoryImpl implements AccountRepository{
@Override
public Account findById(long id){...}
@Override
public void save(Account account){...}
}
여기서 도메인 모델, JPA 엔티티는 그냥 유지한다.
스프링의 의존성 주입은 타입을 기반으로 동작하기 때문에 인터페이스를 구현한 구현체가 알아서 주입된다.( 중복되면 @Qualifier쓰고 )
그래서 나중에 바꿔야 하면, AccountRepositoryImpl만 바꾸면 된다.
(RDB, MongoDB 등 데이터베이스 마음대로 변경가능)
이 원리를 프레젠테이션 레이어에도 적용하자
(즉, Service에서 인터페이스를 설정하자.)
//
@RestController
@RequestMapping("/accounts")
public class AccountController{
private final AccountService account Service;
...
@PatchMapping(...)
public ResponseEntity<Account> patchProperties(...){
...
}
}
// Interface
public interface AccountService{
public Account updateNickNameById(...);
}
// 구현체
@Service
public class AccountServiceImpl implements AccountService{
...
}
헥사고날 아키텍처 구조(포트-어댑터 패턴)
내부 세계(도메인)와 바깥 세계(웹 요청, 데이터베이스 등) 구분

사용자는 입력 어댑터를 통해 API를 호출을 하고, 입력 어댑터는 입력 포트를 통해 입력받은 내용을 ServiceImpl 컴포넌트로 전달한다.
(제어의 역전을 적극적으로 사용하자)
프레젠테이션 레이어에 의존성을 적용할지에 대한 평가
경계를 강제할 수 있다.
외부 세계와 내부 세계에서 벌어지는 모든 일에 일관된 패턴을 적용할 수 있다.
프레젠테이션 레이어에 있는 컴포넌트를 테스트해야 할 때 테스트가 쉬워진다.
의존성 역전을 적용했을 때 얻을 수 있는 실효성이 모호함.
프레젠테이션 레이어는 그대로인데 애플리케이션 레이어가 변경되는 일은 없다.
(프레젠테이션이 바뀌면, 나머지도 싹다 바뀌어야 한다.)
의존성 역전을 적용하지 않아도 도메인 모델은 외부 세계에 독립적
애플리케이션 레이어가 프레젠테이션 레이어에 의존하는 것은 부자연스럽다.
도메인부터 접근하는 방식은 상향식 접근법인다.
(도메인 -> 애플리케이션 서비스 -> 애플리케이션 서비스가 사용할 인터페이스 -> 컨트롤러 및 JPA 시스템 구축 완성)