Domain Driven Design이나 Hexagonal Architecture를 공부하다 보면, 이 둘의 차이가 무엇인지 궁금해진다. 가끔씩 둘이 같은 느낌이 들면서도 차이가 있는 듯 한 헷갈림을 얻고 간다. 그래서 오늘은 이 내용을 공부하면서 느낀 것들을 정리해 보려고 한다.
Layered Architecture(계층적 구조)
계층적 구조는 응용 프로그램들을 - Application이지만, 이에 대한 정확한 기준은 없는 듯 하다 - 분리된 레이어로 각각 분리하는 것을 뜻한다. 각각의 레이어는 Presentation(표현 계층), Application(응용 계층), Domain Layer(도메인 계층) 그리고 Infrat Layer(인프라 계층)으로 분리하며 각각의 역할은 다음과 같다.
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
- Presentation Layer
- 유저 인터페이스나 표현에 관한 로직과 관련된 패키지 혹은 모듈들을 포함한다
- 대표적으로 컨트롤러라 뷰, 뷰모델 등이 될 것이다
- 대표적으로 FastAPI에서는 Pydantic으로 정의한 Schema인 Request, Response Model등이 있을 것이다.
- 위의 코드에서는 Item 클래스가 대표적인 예시일 것이다.
async def create_user(self, password: str, email: str, name: str, db: Session):
query_result = await self.user_repo.get_user_by_email(email=email, db=db)
if query_result:
return None
hashed_password = self.password_service.get_password_hash(password=password)
result = await self.user_repo.create_user(
email=email,
hashed_password=hashed_password,
name=name,
db=db,
)
return result
- Application Layer
- 이 부분은 트랜잭션 관리, DTO의 변환 그리고 도메인 객체의 생성과 접근이 잇다.
- 위의 대부분 파라메터는 유저 혹은 API가 받은 Value Object이다. 왜냐하면 API에서는 Domain 객체를 모르기 때문이다.
- 대표적으로 Service라고 칭하는 파일 이름들이 여기에 해당하는 것 같다.
import datetime
from pydantic import BaseModel, EmailStr
from dataclasses import dataclass
#FastAPI에서 이 정도를 Entity라고 생각하고 싶다.
class User(BaseModel):
id: int
name: str
email: EmailStr
password: str
created_at: datetime.datetime
updated_at: datetime.datetime
#파이썬에서 이 정도를 VO라고 생각하고 싶다.
@dataclass(frozen=True)
class User:
name: str
email: str
password: str
- Domain Layer
- 도메인 계층에는 2가지를 주되게 보면 된다고 생각한다.
- Value Objects : 생명주기가 없는 데이터, 변하지 않는 데이터
- Entities : 생명주기가 있는 데이터, 이력이 생기는 데이터, 즉 데이터베이스와 1:1로 매핑되는 클래스
- VO에 대한 설명부터 시작하겠다
- VO의 경우 값이 같으면 같은 것으로 친다. 즉 동등성을 가진다.
- VO의 경우 변하지 않는다. 동시성을 가질 수 있게 해주며, 불변성을 띄는 것이다. 한 번 만들어지면 수정될 수 없다
- Entity에 대한 설명을 하겠다.
- Entity의 경우 DB와 관련이 크다. 즉 id - 혹은 primary key값 - 이 같아야 똑같다. VO와의 차이는 즉, 식별 가능성이 값을 통해서 나는지 아닌지의 차이일 것 같다.
- 결국 DB와의 종속성 차이이다. 이 데이터가 직접적으로 DB에 영향을 미치는가의 여부라고 생각한다.
- 도메인 계층에는 2가지를 주되게 보면 된다고 생각한다.
class UserRepository(BaseUserRepository):
async def get_user_all(self, db: Session) -> Optional[List[User]]:
query = await db.execute(select(UserModel))
result = query.scalars().all()
if result:
return [self.ConvertToUser(res) for res in result]
return None
- Infra Layer
- 가장 데이터베이스와 가까운 계층이다.
- 관련된 데이터에 접근하는 어댑터와 교환이 이루어지는 곳이다.
- 결론적으로 데이터베이스와 연동하는 구간이다.
결론적으로 정리해 보자면, 가장 밑에 데이터베이스 및 영속성 계층이 있으며, 그 위에 비즈니스 계층이 있고 그러한 비즈니스 계층에서 도메인 로직이 이루어진다. 가장 큰 단점이면서 장점이라고 생각하는 것이 바로 밑으로 내려가면서 종속성을 띄는 것이다. 위로 거슬러 올라가기 힘들다는 단점이자 장점이 있다고 생각한다.
먼저 단점부터 말해보려고 한다.
- 성능 부하 : 계층 간의 통신은 데이터 전환이 필연적으로 이루어진다. 이는 결국 부하를 줄 수 있고 전체 시스템에 속도와 반응성에 영향을 줄 수 있다.
- 변경 어려움 : 다만 이러한 계층 구조를 엄격하게 지키려고 하면, 한 계층의 변경 사항은 다른 계층에 필연적으로 영향을 준다. 유연성이 떨어질 수 있다.
- 데이터 및 로직의 중복 : 계층의 관심사 - 즉 도메인의 분리 - 를 위해 데이터나 로직을 중복할 수 있다.
이번에는 장점이다.
- 모듈성 및 관심사 분리: 각 계층은 특정 기능 측면에 초점을 맞추므로 모듈성이 향상된다. 이러한 관심사 분리로 인해 시스템을 더 쉽게 이해, 유지 관리 및 업데이트할 수 있다.
- 재사용성: 계층 간의 명확한 경계로 인해 코드 재사용이 용이해진다. 공통 기능은 레이어에서 한 번만 구현되어 다른 레이어에서 활용될 수 있으므로 개발 시간과 노력을 절약할 수 있다.
- 확장성: 각 레이어는 수요에 따라 독립적으로 확장될 수 있다. 예를 들어, 애플리케이션 트래픽이 증가하면 비즈니스 로직이나 데이터 레이어에 영향을 주지 않고 프레젠테이션 레이어만 확장할 수 있다.
- 유연성: 레이어는 잘 정의된 인터페이스를 통해 상호 작용하므로, 시스템의 나머지 부분에 영향을 주지 않고 레이어를 수정하거나 교체할 수 있다. 이러한 유연성은 새로운 기술을 도입하거나 구성 요소를 업그레이드할 때 특히 유용하다.
728x90
반응형