헥사고날 아키텍처

  • 소프트웨어 설계에 사용되는 아키텍처 패턴중 하나로 느슨하게 결합된 애플리케이션 구성요소를 만드는 것을 목표로 하는 아키텍처이다.
  • 헥사고날은 도메인 객체, 객체를 조작하는 (포트를 구현한)서비스, 외부에 인터페이스를 제공하는 입출력 포트로 구성되어 있고, 이를 내부라고 본다. 외부는 adapter를 말하며 controller나 repository가 될 수 있다. 외부에서 내부로만 의존성이 존재한다. 따라서 어댑터를 쉽게 교체하거나 수정할 수 있다.
  • 내부에서는 도메인 <- 서비스 <- 포트와 같이 의존성이 존재한다. 즉 도메인은 아무 의존성을 갖지 않으므로 다른 구성요소의 변경 사항이 도메인 객체에 영향을 미치지 않는다. 
  • 비즈니스 문제를 해결하는데 꼭 필요한 기능을 코어로 분류, 나머지 특정 기술에 종속된 기능이나 통신 관련 기능은 외부세계(어댑터)로 분류하는 것이 일반적이다.
도메인 객체 비즈니스 규칙이나 도메인에 대한 상태나 동작을 저장한다.
포트 어댑터와 도메인 서비스 사이의 매개체. 이를 통해 내부와 외부는 통신한다.
어댑터 외부 구성요소로서 포트를 통해 내부와 상호작용한다.

cf) 비즈니스 규칙이란 예를 들어 계좌에서 출금의 경우, 잔액보다 높은 금액을 인출할 수 없다를 말한다.

 

헥사고날 아키텍처 장•단점

장점 비즈니스 로직을 독립적으로 테스트할 수 있어 품질 향상과 개발 속도 향상에 도움이 된다.
단점 아키텍처를 처음 구축할 때 더 많은 시간과 노력이 필요하게 된다.

 

주관적인 폴더 구조는 이와 같다. service는 포트에 대한 구현 클래스를 모아 두는 곳이다.

in은 내부로 요청하는 것을 말하고 out은 내부에서 외부로 요청하는 것을 말한다. port에 대해서 in일 경우에는 usecase, out일 경우에는 port라고 명명한다.

 

간단한 프로젝트 구현

 

Domain 객체

@Builder
@AllArgsConstructor
@Getter
@NoArgsConstructor
public class AccountDomain {
    private Long id;
    private BigDecimal balance;

    public boolean withdraw(BigDecimal amount) {
        if(balance.compareTo(amount) < 0) {
            return false;
        }
        balance = balance.subtract(amount);
        return true;
    }

    public void deposit(BigDecimal amount) {
        balance = balance.add(amount);
    }

}

 

In Port

public interface DepositUseCase {
    void deposit(Long id, BigDecimal amount);
}
public interface WithdrawUseCase {
    boolean withdraw(Long id, BigDecimal amount);
}

 

Out Port

public interface LoadAccountPort {
    AccountDomain load(Long id);
}
public interface SaveAccountPort {
    void save(AccountDomain accountDomain);
}

 

In Adapter

@RestController
@RequiredArgsConstructor
@RequestMapping("/account")
public class AccountAccessAdapter {
    private final DepositUseCase depositUseCase;
    private final WithdrawUseCase withdrawUseCase;

    @PostMapping("/deposit/{id}/{amount}")
    public void deposit(@PathVariable Long id, @PathVariable BigDecimal amount) {
        depositUseCase.deposit(id, amount);
    }

    @PostMapping("/withdraw/{id}/{amount}")
    public void withdraw(@PathVariable Long id, @PathVariable BigDecimal amount) {
        withdrawUseCase.withdraw(id, amount);
    }
}

 

Out Adapter

@Repository
@RequiredArgsConstructor
public class AccountPersistenceAdapter implements LoadAccountPort, SaveAccountPort {
    private final AccountJpaRepository accountJpaRepository;
    private final AccountMapper accountMapper;
    @Override
    public AccountDomain load(Long id) {
        Optional<AccountEntity> oae = accountJpaRepository.findById(id);
        if(oae.isPresent()) {
            return accountMapper.toDomain(oae.get());
        }
        else{
            throw new NoSuchElementException();
        }
    }

    @Override
    public void save(AccountDomain accountDomain) {
        AccountEntity accountEntity = accountMapper.toEntity(accountDomain);
        accountJpaRepository.save(accountEntity);
    }
}
public interface AccountJpaRepository extends JpaRepository<AccountEntity, Long> {
}
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class AccountEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private BigDecimal balance;
}

domain과 entity를 따로 둔 이유는 위에 헥사고날 아키텍처의 장점 때문이다. 비즈니스 로직을 독립적으로 테스트하기 위함

 

@Component
public class AccountMapper {
    public AccountDomain toDomain(AccountEntity accountEntity) {
        return AccountDomain.builder()
                .id(accountEntity.getId())
                .balance(accountEntity.getBalance())
                .build();
    }

    public AccountEntity toEntity(AccountDomain accountDomain) {
        return AccountEntity.builder()
                .id(accountDomain.getId())
                .balance(accountDomain.getBalance())
                .build();
    }
}

 

Entity와 Domain에 각각 toDomain, toEntity를 안 만들고 Mapper라는 클래스를 만들고 adapter에 만들어준 이유는 내부에서 외부로 의존성을 없애기 위함이다. 

' > 실전 자바 소프트웨어 개발' 카테고리의 다른 글

7장 주요 내용 정리  (0) 2024.07.01
6장 주요 내용 정리  (0) 2024.06.29
5장 주요 내용 정리  (0) 2024.06.27
4장 주요 내용 정리  (0) 2024.05.28
3장 주요 내용 정리  (0) 2024.05.26

+ Recent posts