1. 아키텍처 - Hexagonal
이번 프로젝트는 헥사고날 아키텍처를 기반으로 진행될 예정입니다. 헥사고날 아키텍처는 비즈니스 로직과 인터페이스, 인프라스트럭처를 분리하여 책임을 명확히 하고 각 컴포넌트의 독립성을 높여, 유지보수성과 확장성을 극대화하는 데 도움을 줍니다. 객체지향 언어인 자바를 사용함에 따라 SOLID 원칙을 효과적으로 적용할 수 있는 헥사고날 아키텍처를 선택하였습니다.
1.1 헥사고날 아키텍처
헥사고날 아키텍처(Hexagonal Architecture)는 소프트웨어 설계 패턴으로 Alistair Cockburn에 의해 제안되었습니다. 애플리케이션의 비즈니스 로직을 외부 세계와 분리하여 유연하고 테스트하기 쉬운 시스템을 설계하는 데 중점을 둔 구조입니다. "포트와 어댑터" 구조로 구성되어 있으며, 비즈니스 로직을 중심으로 다양한 외부 인터페이스를 연결할 수 있도록 합니다.
1.2 주요 구성
헥사고날 아키텍처는 "포트와 어댑터" 모델이라고도 불리며 다음과 같은 주요 요소로 구성됩니다.
그림 출처 : explicit architecture – @hgraca (herbertograca.com)
explicit architecture – @hgraca
This post is part of The Software Architecture Chronicles, a series of posts about Software Architecture. In them, I write about what I’ve learned about Software Architecture, how I think of it, and how I use that knowledge. The contents of this post mig
herbertograca.com
- 비즈니스 로직(domain)
- 애플리케이션의 핵심 기능과 규칙을 포함
- 비즈니스 규칙과 객체 모델이 구현
- 포트(ports) /application
- 비즈니스 로직(domain)과 외부 세계(infrastructure 및 interface) 간의 인터페이스를 정의
- 주로 서비스 인터페이스 형태로 존재하며 애플리케이션의 기능을 외부에 노출
- 어댑터(adapters)
- 사용자와의 상호작용을 처리하고, 외부 요청을 도메인 포트에 전달
- 비즈니스 로직의 실행 결과를 외부에 반환하여 사용자에게 필요한 정보를 제공
- 데이터베이스 접근, 외부 API 호출 등과 같은 기술적 세부사항을 관리하여 도메인 로직과 분리
1.3 장단점
- 장점
- 유연성: 비즈니스 로직과 외부 시스템이 분리되어 있어 외부 변화가 비즈니스 로직에 영향을 미치지 않음
- 테스트 용이성: 비즈니스 로직을 독립적으로 테스트할 수 있어 단위 테스트가 쉽고 효과적
- 재사용성: 동일한 비즈니스 로직을 여러 어댑터에서 재사용할 수 있어 코드 중복을 줄이고 유지보수를 용이
- 확장성: 새로운 기능이나 외부 시스템 통합을 쉽게 추가할 수 있어 시스템의 확장성이 높음
- 변화 저항력: 비즈니스 로직과 외부 시스템 간의 결합도를 낮춰 시스템이 변화에 강하도록 설계
- 단점
- 복잡성: 아키텍처가 복잡해질 수 있음
- 초기 설정 비용: 구조를 설정하고 포트 및 어댑터를 설계하는 데 초기 시간과 노력이 더 필요
- 오버헤드: 여러 레이어와 인터페이스를 정의하는 과정에서 발생하는 오버헤드가 성능에 영향을 줄 수 있음
- 일관성 유지: 여러 어댑터가 존재할 때, 각 어댑터의 일관성을 유지하는 것이 어려울 수 있음
2. 프로젝트 아키텍처
현재 진행할 게시판 프로젝트는 소규모 학습용 프로젝트로 도메인 별 세부 모듈화는 진행하지 않고 헥사고날 아키텍처 기반의 큰 범위로 패키지를 구성할 예정이었습니다. 그러나 좀 더 실무와 가까운 학습을 위해 도메인 별로 패키지를 분리하여 다시 내부에 별도로 패키지를 분리한 구조를 선택하였습니다. 미래에 기능 추가와 같은 확장이 필요하게 된다면 이러한 구조가 코드 간의 응집도를 높이고 확장성이 좋은 방향이라고 생각하였습니다.
com.kwang.board
├── user
│ ├── application
│ │ ├── service
│ │ │ ├── UserService.java
│ │ ├── dto
│ │ │ ├── UserDTO.java
│ ├── domain
│ │ ├── model
│ │ │ ├── User.java
│ │ ├── repository
│ │ │ ├── UserRepository.java
│ ├── adapters
│ │ ├── persistence
│ │ │ ├── JpaUserRepository.java
│ │ ├── security
│ │ │ ├── WebSecurityConfig.java
│ │ ├── controller
│ │ │ ├── UserController.java
│ │ ├── mapper
│ │ │ ├── UserMapper.java
│ ├── usecase
│ │ ├── RegisterUserUseCase.java
│ │ ├── LoginUseCase.java
│ │ ├── UpdateUserUseCase.java
│ │ ├── DeleteUserUseCase.java
├── post
│ ├── application
│ │ ├── service
│ │ │ ├── PostService.java
│ │ ├── dto
│ │ │ ├── PostDTO.java
│ ├── domain
│ │ ├── model
│ │ │ ├── Post.java
│ │ ├── repository
│ │ │ ├── PostRepository.java
│ ├── adapters
│ │ ├── persistence
│ │ │ ├── JpaPostRepository.java
│ │ ├── controller
│ │ │ ├── PostController.java
│ │ ├── mapper
│ │ │ ├── PostMapper.java
│ ├── usecase
│ │ ├── CreatePostUseCase.java
│ │ ├── UpdatePostUseCase.java
│ │ ├── DeletePostUseCase.java
│ │ ├── ViewPostUseCase.java
│ │ ├── RecommendPostUseCase.java
├── comment
│ ├── application
│ │ ├── service
│ │ │ ├── CommentService.java
│ │ ├── dto
│ │ │ ├── CommentDTO.java
│ ├── domain
│ │ ├── model
│ │ │ ├── Comment.java
│ ├── repository
│ │ │ ├── CommentRepository.java
│ ├── adapters
│ │ ├── persistence
│ │ │ ├── JpaCommentRepository.java
│ │ ├── controller
│ │ │ ├── CommentController.java
│ │ ├── mapper
│ │ │ ├── CommentMapper.java
│ ├── usecase
│ │ ├── CreateCommentUseCase.java
│ │ ├── UpdateCommentUseCase.java
│ │ ├── DeleteCommentUseCase.java
│ │ ├── ViewCommentUseCase.java
├── photo
│ ├── application
│ │ ├── service
│ │ │ ├── PhotoService.java
│ │ ├── dto
│ │ │ ├── PhotoDTO.java
│ ├── domain
│ │ ├── model
│ │ │ ├── Photo.java
│ │ ├── repository
│ │ │ ├── PhotoRepository.java
│ ├── adapters
│ │ ├── persistence
│ │ │ ├── JpaPhotoRepository.java
│ │ ├── controller
│ │ │ ├── PhotoController.java
│ │ ├── mapper
│ │ │ ├── PhotoMapper.java
│ ├── usecase
│ │ ├── UploadPhotoUseCase.java
│ │ ├── DeletePhotoUseCase.java
│ │ ├── ViewPhotoUseCase.java
├── global
│ ├── config
│ │ ├── ApplicationConfig.java
│ ├── exception
│ │ ├── CustomException.java
│ ├── security
│ │ ├── SecurityUtils.java
│ ├── util
│ │ ├── MapperUtils.java
위의 패키지 모델을 기반으로 진행될 예정이며 현재 내부 구성은 클래스 설계 전 초기 구상 형태로 추후 변경될 가능성이 있습니다.
2.1 계층 설명
- application
- 목적: 비즈니스 로직과 도메인 모델 간의 상호작용 담당
- 주요 역할
- 비즈니스 규칙 구현 클래스(service 클래스) 정의
- DTO 정의
- domain
- 목적: 핵심 비즈니스 로직과 도메인 모델 정의
- 주요 역할
- 비즈니스 엔티티를 정의하여 도메인 모델 구성
- 도메인 엔티티 영속성 관리를 위한 리포지토리 정의
- adapters
- 목적: 외부 시스템과의 통신 처리 및 기술 세부사항 관리
- 주요 역할
- JPA를 사용한 데이터베이스 접근 구현
- 보안 관련 설정 정의
- 사용자 인터페이스와 상호작용을 처리하고 HTTP 요청을 전달할 Controller 정의
- 도메인 모델과 DTO 간 변환을 수행할 매퍼 정의
- usecase
- 목적: 비즈니스 규칙 정의
- 주요 역할
- 각 도메인별 비즈니스 규칙을 인터페이스로 정의
- global
- 목적: 애플리케이션의 공통적인 처리 담당
- 주요 역할
- 애플리케이션 전반적 설정 관리
- 애플리케이션 커스텀 예외 클래스 정의
2.2 패키지 다이어그램
위의 구조로 구성된 패키지 다이어그램입니다. service는 usecase를 상속받아 구체적인 비즈니스 로직을 구현하는 곳입니다. 구체적인 서비스 구현이 usecase 인터페이스에 따라 동작해야 하기 때문에 service가 usecase를 의존하게 됩니다. 구현체인 service는 domain 계층을 의존하여 비즈니스 로직을 수행하게 됩니다. service는 domain 객체 없이 비즈니스 로직을 구현할 수 없으므로 service는 domain을 의존하게 됩니다. adapters 패키지의 persistence는 실제 데이터베이스와 상호작용하는 계층이며 domain에 정의된 repository 인터페이스를 상속받아 구현체를 제공합니다. Spring Data JPA 등을 사용하여 이러한 리포지토리 인터페이스의 구체적인 구현을 담당하며 이를 통해 persistence는 repository를 의존하게 됩니다. controller는 사용자 요청을 받아 이를 service에 전달하여 처리합니다. 즉 controller는 service 계층을 호출하는 역할을 하며 비즈니스 로직은 service에서 처리되기 때문에 controller가 service를 의존하게 됩니다. 그리고 각 application과 adapters 계층에서 global을 호출하여 사용하며 의존성을 가지게 됩니다.
결론으로 클린 코드 원칙을 따라 domain은 아무 계층에 의존하지 않고 service는 domain을 의존하게 되고 controller는 service를 의존하는 헥사고날 아키텍처를 기반으로 설계되었습니다.
[프로젝트] 3. 패키지 구조
헥사고날 아키텍처를 적용하여 패키지 구조를 정리해봤다.시스템 구성과 동작원리 그리고 시스템의 구성환경 등을 설명하는 설계도이다.비즈니스 요구 사항을 만족하는 시스템을 구축하기 위
velog.io
2024.10.05 수정, 패키지 구조 수정 - photo 추가
'개인 프로젝트 > Toy & Side' 카테고리의 다른 글
[TOY] 설계 - 유스케이스 (1) | 2024.10.13 |
---|---|
[TOY] 설계 - 도메인 (1) | 2024.10.09 |
[TOY] 설계 - 데이터베이스 DataBase (0) | 2024.10.06 |
[TOY] 설계 - 유스케이스 (0) | 2024.10.01 |
[TOY] 게시판 프로젝트 개요 (1) | 2024.09.28 |