DEV/Architecture

헥사고날 아키텍처 시리즈 9. 주의사항

행운개발자 2024. 3. 20. 23:23
728x90

헥사고날 아키텍처를 적용해보면서 제가 직접 고민하고 깨닫고 실수했던 사항들입니다. 직접 프로젝트를 하면서 생각나는 부분들이 있으면 채워두겠습니다.

구조 관점

  1. 멀티 모듈에서 모듈은 배포 단위로 구성하는 것이 좋습니다. 프로젝트가 모듈 A,B,C로 나누어져있고 모듈 C가 모듈 A,B를 의존하고 있는 상황이라면 하나의 모듈 안에 패키지 구조로 세부 도메인 A,B,C를 나누는 것을 고려해보아야합니다. 세부 도메인을 별도의 모듈로 나누는 시점은 배포를 따로 해야하는 타이밍일 수 있습니다.

애플리케이션 계층

  1. 트랜잭션 처리는 애플리케이션 계층에서 이루어저야한다. @Transactional을 사용한다면 AOP의 특징을 반영한 아래의 주의 사항을 참고하자
  2. // TODO @Transactional 사용시 주의사항
  3. 트랜잭션을 처리할 때 트랜잭션의 범위를 좁히는 것이 중요하다.
  4. Port를 통해서 엔티티 정보를 조회할 때에는 항상 최소 정보만을 조회하는 것이 좋습니다. 객체 전체가 필요한지 컬럼 하나만 필요한지 고민해봐야합니다.
  5. 반복적인 엔티티의 ID 조회를 여러번 반복해서 데이터베이스 커넥션을 낭비하는 것을 방지하기 위해서, 적절하게 데이터베이스 영역에서 JOIN을 사용할 수 있는지 고민해봐야합니다. 최대한 많은 엔티티를 조회하고 애플리케이션 계층에서 모든 연산을 수행하는 것은 비효율적입니다

영속성 계층

  1. 애플리케이션에서는 객체의 필드를 CamelCase로, 데이터베이스에서는 under_score를 주로 사용합니다. CamelCase를 under_score로 변환하는 과정을 Mapper에서 수행하는 것이 적절한지 고민해보면 좋습니다.
  2. 엔티티의 수정사항에 대한 히스토리의 저장이 필요한 경우, 프레임워크에서 제공하는 이벤트를 사용할 수 있는지 검점하는 것이 좋습니다. (ApplicationEventPublisher, @EventListener)
  3. 데이터베이스의 특징을 명확하게 이해하고 인덱스를 추가하는 것이 좋습니다. MySQL에서 복합인덱스가 생성될 때 좌측 접두사 원칙을 따릅니다. 이는 복합 인덱스가 생성된 컬럼의 순서대로 왼쪽에서부터 인덱스를 사용할 수 있다는 것을 의미합니다. 따라서, UNIQUE_A_B 복합 인덱스는 column_a에 대한 조회에서는 인덱스를 활용할 수 있지만, column_b에 대한 조회에서는 이 인덱스를 활용하지 못할 수 있습니다. 따라서 column_b만 사용하는 쿼리의 경우 복합 인덱스를 활용하지 못할 수 있어 별도의 인덱스를 생성해주는 것이 좋습니다. 더 자세한 설명은 다음 포스팅에서 작성하겠습니다.
    CREATE TABLE IF NOT EXISTS `MyTable` (
      `id` bigint NOT NULL AUTO_INCREMENT,
      `column_a` bigint NOT NULL,
      `column_b` bigint NOT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `UNIQUE_A_B` (`column_a`,`column_b`),
      KEY `INDEX_B` (`column_b`),
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
    
  4. // TODO MySQL 인덱스, 복합 인덱스

도메인 계층

  1. 도메인 객체를 생성하는 경우의 수가 아직 1개뿐이라도 생성자의 파라미터로 null을 입력해야하는 경우에는 오타를 방지하고 의미를 명시적으로 부여하기 위해 정적 펙토리 메서드를 사용하는게 좋습니다.

테스트 코드

  1. 가장 간편하게 테스트 코드를 실행할 수 있는 H2 데이터베이스를 사용해서 테스트 할 수 있어야 한다
  2. 제3자가 테스트 코드를 실행할 때 번거로운 작업을 수행하지 않아도 되도록 schema를 자동으로 반영할 수 있는 구조를 갖추어야 한다
  3. 필요하다면 운영 환경과 최대한 유사하게 LOCAL MySQL 데이터베이스를 사용해서 테스트를 수행할 수 있어야 한다

로깅

  1. logger의 첫 번째 파라미터는 format을 나타냅니다. 첫 번째 인자를 format의 역할에 맞게 잘 사용하고 있는지 확인해야 합니다.
@Override
public UserId create(CreateUserCommand command) {
    User user = new User(null,
            command.getEmail(),
            command.getNickname(),
            command.getPassword());
    log.info("msg : {}, email : {}, nickname :{}, pwd : {}",
            "createUser", command.getEmail(), command.getNickname(),  command.getPassword());
    return createUserPort.create(user);
}
// org.slf4j.Logger
public void info(String format, Object... arguments);
728x90