반응형
<헥사고날 아키텍처(포트 앤 어뎁터 아키텍처)_Express example>
클린 아키텍처
- 클린 아키텍처가 갖는 기본적인 목적은 관심사의 분리다. 각각 계층별로 관심사를 나누고, 도메인 (비즈니스 로직) 중심으로 설계해야 한다. 또한 프레임워크나 외부 UI에 의존하지 않아야 한다.
헥사고날 아키텍처 (포트 앤 어뎁터 아키텍처)
- 헥사고날 아키텍처는 클린 아키텍처와 거의 비슷하다. 둘은 도메인 (비즈니스 로직)을 인프라 (단순히 AWS와 같은 인프라 뿐만 아니라 view layer와 도 포함)에서 분리하는 것이다.
→ 위 그림은 클린 아키텍처 그림이다. 엔티티가 가장 안쪽에 있고, 의존성은 밖에서 안쪽으로만 존재한다. 그렇기 때문에 엔티티에 접근하기 위해선 계층을 전부 거쳐야 들어올 수 있다.
→ 해당 그림은 헥사고날 아키텍처의 그림이다. 클린아키텍처 그림과 동일하게 의존성은 밖에서 안쪽으로, 도메인 비지니스 로직에 접근하기 위해선 계층(여기선 어뎁터, 포트)를 거쳐야 한다.
결론적으로 클린 아키텍처와 헥사고날 아키텍처는 위 그림에서 보이듯 구현의 차이만 있을 뿐 핵심적인 흐름은 거의 비슷하다.
헥사고날 아키텍처의 흐름
- 위의 그림을 보면서 다시 설명하자면 Browser나 App에서 사용자의 요청이 들어오고, 해당 요청들은 Controller로 표시된 Adapter로 들어오고, Service로 표시된 Port에서 인터페이스를 확인한다. (확인한다? 맞춘다? 사실 'implement' 때문에 interface 안맞으면 애초에 실행도 안됨)
- 포트를 통해 들어온 요청은 비즈니스 로직으로 전달되고, 해당 로직이 실행된다.
- 이 때 외부에 있는(의존성이 없는 or 약한) 데이터베이스에 접근할 일이 있으면 다시 Repository로 표시된 Port를 통해 인터페이스를 확인하고, DAO로 표시된 Adapter로 나가서 데이터베이스에 접근한다. 그리고 요청을 리턴한다.
- Adapter에는 비즈니스 로직이 존재하면 안되고, Port는 Interface로 구현됨.
Express로 구현한 Port and Adapter 예제 코드
Nest.js의 기본 레이어와 동일하게 controller, service, repository로 나눠서 구현 예제 코드 링크 (깃헙)
controller
import { Request, Response, NextFunction } from "express";
import { IConnectController, IConnectService } from "./connect.interface";
export class ConnectController implements IConnectController {
constructor(private connectService: IConnectService) {}
connect = async (req: Request, res: Response, next: NextFunction): Promise<any> => {
try {
const { test } = req.body;
const result = await this.connectService.find({ test });
res.status(200).json({ result });
} catch (error) {
next(error);
}
};
}
- controller에선 service port를 주입받고
import { IConnectService, IConnectRepository } from "./connect.interface";
export class ConnectService implements IConnectService {
constructor(private connectRepository: IConnectRepository) {}
find = async (test: string): Promise<any> => {
try {
const result = await this.connectRepository.find({ test });
return result;
} catch (error) {
throw error;
}
};
}
- service에선 repository port를 주입받고
repository
import TodayCounter from "../models/count";
import { IConnectRepository } from "./connect.interface";
export class ConnectRepository implements IConnectRepository {
find = async (test: string): Promise<any> => {
await TodayCounter.find({ name: test });
return;
};
}
interface
import { Request, Response, NextFunction } from "express";
export interface IConnectService {
find: (test: string) => Promise<any>;
}
export interface IConnectRepository {
find: (test: string) => Promise<any>;
}
export interface IConnectController {
connect: (req: Request, res: Response, next: NextFunction) => Promise<any>;
}
- port의 역할은 interface가 맡는다.
router
import express from "express";
import { IConnectController } from "../connect/connect.interface";
import { ConnectController } from "../connect/connect.controller";
import { ConnectService } from "../connect/connect.service";
import { ConnectRepository } from "../connect/connect.repository";
const router = express.Router();
// typedi를 못쓰고 여기서 하나하나 전부 인스턴스화를..
const connectRepository = new ConnectRepository();
const connectService = new ConnectService(connectRepository);
const connectController = new ConnectController(connectService);
router.get("/connect", connectController.connect);
export default router;
헥사고날 아키텍처 장점
- 테스트에 용이
- 각각의 역할이 명확하고, 의존성도 명확하기 때문에 테스트가 용이해진다.
- 예를 들어 비즈니스로직은 외부 (ex. database)와 분리되어 있기 때문에 비즈니스 로직만 따로 테스트가 가능하다. 반대의 경우도 가능함.
- 관심사 분리
- 말 그대로 각각의 역할이 명확하게 분리되기 때문에 코드를 처음 접하는 사람도 각각의 역할을 쉽게 알 수 있다.
- 에러에 대한 책임도 어느 계층의 책임인지 명확히 할 수 있다.
- 기능의 확장이 필요할 때 매우 용이함.
- 지속가능한 코드를 작성할 수 있음.
해결하지 못한 문제
- express로 헥사고날 아키텍처를 구현하다 보니 typedi를 활용한 IoC구현 문제는 해결하지 못했다.
- 의존성 주입받는 타입을 interface로 하고 typedi를 통해 container.get()을 하게되면 의존성 주입이 어느 단계에서 끊기는 문제여서 일단 typedi를 사용하지 않고 직접 인스턴스화를 통해 만들어서 테스트를 진행했다.
- express로 해당 아키텍처 구현하다보니 Nest.js가 편한 프레임워크였구나 싶었다. Nest.js은 기본적으로 IoC, DI 개념이 들어가있고, interface를 통해 헥사고날 아키텍처 패턴을 구현해도 알아서 잘 주입해준다..
반응형
'Architecture' 카테고리의 다른 글
비즈니스 로직(Service layer)의 역할 (0) | 2023.06.01 |
---|---|
AWS Lambda - SQS를 활용한 인프라 아키텍처 재설계 ( + S3, 맥미니?) (0) | 2023.02.27 |