프레임워크와 무관하다. 아키텍처는 특정 소프트웨어 라이브러리에 의존하지 않는다. 특정 시스템에 제한된 제약 조건을 두지 않고, 다양한 프레임워크를 도구로 사용 할 수 있어야 한다.
비즈니스 규칙은 UI, 데이터베이스, 웹 서버 또는 기타 외부 요소 없이 테스트 할 수 있어야 한다.
UI와 무관하다. UI는 시스템의 나머지 부분을 변경하지 않고도 쉽게 변경할 수 있다. 예를 들어 비즈니스 규칙을 변경하지 않고도 웹 UI를 콘솔 UI로 교체할 수 있어야 한다.
데이터베이스에 독립적. Oracle을 SQL Server, MySQL Server, MongoDB, BigTable, CouchDB 등의 다른 것으로 교체할 수 있어야 한다. 비즈니스 로직이 데이터베이스에 바인딩되면 안될 것이다.
비즈니스 로직은 외부 세계와 무관하게 작동할 수 있어야 한다.
이러한 요구사항은 결과적으로 애플리케이션을 여러 계층으로 나누게 한다. 각 계층은 다른 계층과 독립적이어야 하는데, 이는 독립적으로 구현하고 독립적으로 테스트가 가능해야 함을 의미한다. Martin의 아키텍처는 4개의 레이어로 구성된다.
Entities
Use Cases
Controllers
Devices
각 동심원은 소프트웨어 영역을 나타낸다. 원의 바깥쪽에는 사용자가 자리잡는다.
이 아키텍처의 중요 규칙은 종속성 규칙이다. 이 규칙은 코드 종속성이 안쪽만을 가리키도록 한다. 예를 들어 Use Cases는 어떤 Controller가 사용될지, 이 Controller가 어떤 일을 할지 알 필요도 없고 알아서도 안된다. Use Case는 Entities 만 알고 있으면 된다.
우리는 아래와 같이 4개의 레이어를 가지도록 구조화 했다.
Model
Repository
Use Case
Delivery
Model
모델은 엔티티와 동일하며 모든 레이어에서 사용한다. 여기에는 Struct와 메서드를 저장한다. 모델의 예로는 서비스 사용자, 쇼핑몰 상품, 블로그 컨텐츠 등이다. 블로그 컨텐츠는 아래와 같은 모델을 가질 것이다.
type Article struct {
ID int64 `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
}
모델은 비즈니스 규칙을 담는 객체로 애플리케이션의 가장 밑바닥에 위치하며, 거의 변하지 않는다. 애플리케이션 기능이 변경된다고 해서, 혹은 디바이스가 추가된다고 해서(모바일 애플리케이션이 추가 된다거나) 변하지 않는다. 기능이 변경될 때마다 엔티티가 변하면 설계에 문제가 있는 건 아닌지 검토를 해봐야 한다.
Repository
많은 언어들이 자원(Resource)에 대한 리포지토리를 만든다. 이 리포지토리는 데이터를 관리한다. 즉 자원을 저장하고 검색하는 역할을 한다. 우리가 다룰 시나리오에서는 "사용자"가 객체가 된다.
이 예제의 경우 Repository는 Article의 저장, 읽기, 검색, 업데이트 등의 작업을 수행한다. Repository는 데이터의 CRUD(쓰기,읽기,업데이트,삭제) 작업만 할 뿐 비즈니스 로직을 가지지 않는다. 즉 데이터베이스의 일반적인 기능만을 담당한다.
Repository 계층은 비즈니스로직과 데이터베이스를 분리하는 역할을 한다. 즉 MySQL, MongoDB, DynamoDB, Postgresql 등 무엇을 사용할지가 여기에서 결정된다. 결정된 데이터베이스에 대한 일반적인 기능들을 제공하기 때문에 비즈니스 영역은 데이터베이스를 신경쓸 필요가 없다.
Use Case
이 영역에서 비즈니스 프로세스를 처리한다. REST API 서비스의 경우, 사용자 요청이 여기에서 처리된다. 이 계층은 사용할 Repository를 결정하고, Repository의 기능을 호출해서 비즈니스 데이터를 제공하고 처리한다. Repository로 부터 읽은 데이터를 계산하는 모든 작업이 여기에서 수행된다.
이 예제에서 Use Case는 사용자가 원하는 Article를 찾아서 제공하거나 새로운 Article를 저장하는 등의 작업을 수행 한다.
Delivery
프리젠테이션을 담당하는 영역으로 데이터가 표시되는 방식을 결정한다. 데이터를 REST API, HTML, gRPC 형태로 프리젠테이션한다. 이 영역은 사용자의 요청을 일선에서 받아내는 역할을 하는데, 데이터가 어떤 형식이든지 Use Case가 사용할 수 있는 형식으로 변환해서 전송한다. 따라서 Use Case는 입/출력 데이터의 형식에 상관없이 데이터만 처리 할 수 있다.
하지만 보통의 웹 애플리케이션들은 REST API, 데이터 입출력은 JSON으로 통일하는 경우가 많다.
레이어간 통신
이들 4개의 레이어는 아래와 같은 구성을 가질 것이다.
화살표는 요청의 흐름이다. 화살표는 바깥에서 안쪽으로 향하고 있는데, 이는
안쪽에 있는 레이어는 바깥쪽의 레이어를 알 필요가 없으며
바깥쪽의 레이어는 바로 안쪽에 있는 레이어만 알고 있으며 안쪽레이어에서 제공하는 인터페이스로 접근
함을 의미한다.
예를 들어 Repository는 Repository를 사용하는 상위 레이어를 위해서 아래와 같은 인터페이스를 제공 할 것이다.
Use Case 레이어는 이 인터페이스를 이용해서 Repository와 커뮤니케이션 한다. 이를 위해서 반드시 구현을 해야 한다.
Use Case 도 인터페이스로 구축 할 것인지 하는 것은 고민이 필요하다. 이론상 Use case도 인터페이스로 만들어 두면, Delivery 레이어와 완전히 분리가 가능하긴 하다. 예를 들어 gPRC를 이용한 모바일 앱, REST API를 이용한 웹 애플리케이션 등 다양한 디바이스로의 대응이 가능하다. 하지만 일반적인 REST API로만 서비스 한다면, 인터페이스를 구축할 필요는 없을 것이다. 일단 직접 구현하고 나중에 필요 할 때, 인터페이스로 쪼개면 된다.
레이어별 테스트
깔끔한 구조라는 것은 독립되어 있다는 것을 의미한데. 독립에는 테스트도 포함된다.
Models 레이어
Struct에 선언된 메서드가 있는 경우에만 테스트한다. 이를 JSON이나 XML로 변환거나, 검증하는 메서드들을 가질 수 있는데, 이럴때 테스트를 작성 할 수 있을 것이다. 다른 레이어와 독립적으로 테스트 할 수 있다.
Repository 레이어
데이터베이스를 다루는 영역이기 때문에, 다른 레이어와의 통합테스트를 수행하는 것이 더 좋다. github.com/DATA-DOG/go-sqlmock 등의 mock를 이용한 테스트도 있다.
Use case 레이어
Use case는 Repository 레이어에 의존한다. Mockery와 같은 mockup 툴을 이용하여 Repository 레이어가 완성되기 전에 Use case를 완성 할 수 있다.
Delivery 레이어
http REST API를 사용하는 경우 golang의 httptest 패키지를 이용해서 테스트를 할 수 있다. 여전히 Use case에 따라서 Mockery를 사용해야 할 수 있다.
연습
실제 코드를 만들어보자. 코드 스트럭처는 아래와 같다. 전체코드는 github에서 다운로드 할 수 있다.
이 문서는 소프트웨어 구조를 잡는 것을 목표로 한다. 데이터베이스까지 구현하면 버거로울 것 같아서, 실제 MySQL 연결을 넣지 않았다. 위 코드는 MySQL을 저장소로 사용하는 Repository 구현체다. Repository 인터페이스의 모든 메서드를 구현한다.
저장소로 MongoDB도 사용해야 한다면, repository/mongo 밑에 mongodb 구현을 만들면 된다.
Use Case 구현
이제 MySQL 저장소로 부터, 데이터를 읽어와서 처리하는 Use Case를 구현 할 것이다. 마찬가지로 실제 비즈니스로직은 포함하지 않는다.
Contents
개요
제안하는 구조
Model
Repository
Use Case
Delivery
레이어간 통신
레이어별 테스트
연습
프로젝트 초기화
Domain 설정
Repository 구현
Use Case 구현
Delivery
main
정리
해 볼 것들
참고
Recent Posts
Archive Posts
Tags