📌 아키텍처 경계를 완벽하게 만드는 것은 힘들다.
- 완벽한 경계는 구현과 유지에 많은 비용과 노력이 필요함.
- 쌍방향의 다형적 경계 인터페이스
- Input과 Output을 위한 데이터 구조
- 독립적 컴포넌트로 컴파일 및 배포할 수 있는 구조 및 의존성 관리
- YAGNI(You Aren’t Gonna Need It) 원칙에 따라, 모든 경계를 미리 만들기보다는 필요할 때 경계를 도입하는 접근도 고려해볼 수 있음 → 부분적 경계
📌 전략 1: 마지막 단계를 건너뛰기 (Partial Boundary)
- 이 전략은 논리적으로는 컴포넌트를 분리하지만, 물리적으로는 분리된 배포를 생략하는 방식임.
- 즉, 컴포넌트 내부적으로는 입력/출력 인터페이스, 데이터 구조, 책임 분리를 모두 갖추고 있음.
그러나 각 계층을 다른 배포 단위로 나누지 않고 하나의 컴포넌트로 묶어 컴파일하고 배포함. - 이렇게 마지막 분리 단계를 생략함으로써 배포의 복잡도와 관리 비용을 줄이는 것이 핵심임.
- 이러한 전략은 초기에는 배포와 운영을 단순화하는 장점이 있지만, 시간이 지남에 따라 경계가 흐려지고 컴포넌트 간 의존성이 강화되며, 경계의 의미가 퇴색되는 위험을 수반함.
결국 나중에는 경계를 다시 복원하거나 분리해야 하는 비용과 기술적 부채로 되돌아올 수 있음.
📌 전략 2: 일차원 경계 (Strategy Pattern)
- 이 전략은 인터페이스로 경계를 느슨한 결합으로 만듦
(Client → Interface ← Impl) - 이 방식은 추후 컴포넌트가 분리될 가능성을 대비해 구조적 유연성을 확보할 수 있음.
// 완벽한 경계를 위한 쌍방향 다형적 인터페이스 예시
interface InputBoundary {
void handle(InputData input);
}
interface OutputBoundary {
void present(OutputData output);
}
class Interactor implements InputBoundary {
private final OutputBoundary output;
Interactor(OutputBoundary output) {
this.output = output;
}
public void handle(InputData input) {
// ...도메인 처리
output.present(new OutputData(...));
}
}
class Presenter implements OutputBoundary {
public void present(OutputData output) {
// UI 렌더링 로직
}
}
// 전략 패턴을 사용한 단순화된 경계 예시
interface ServiceBoundary {
OutputData execute();
}
class ServiceImpl implements ServiceBoundary {
public OutputData execute() {
return new OutputData("결과");
}
}
class Client {
private final ServiceBoundary service;
Client(ServiceBoundary service) {
this.service = service;
}
public void run() {
OutputData result = service.execute();
System.out.println("Service 결과: " + result.value());
}
}
record OutputData(String value) {}
- 코드를 보면, 전략 2는 완벽한 구조에 비해 설계와 구현이 훨씬 간단하다는 장점이 있음.
- 하지만, ServiceImpl이 Client를 직접 참조하게 되면 경계가 무너질 수 있음을 주의
📌 전략 3: 퍼사드 패턴 (Facade Pattern)
- 가장 단순한 경계 설정 방식 중 하나
- Facade 클래스를 통해 [인터페이스 없이] 모든 서비스 클래스를 간접 호출함.
- 즉, 퍼사드는 클라이언트가 Core 계층(Service)에 직접 접근하지 못하게 하는 중간 계층 역할을 함.
Client → Facade → Service1 / Service2 / Service3 ..
- Client는 결국 서비스 클래스에 추이 종속성을 가짐.
- 정적 언어일 경우, Service에 변화가 생기면 Client도 재컴파일이 필요함.
Client → Interface Adapter ← Service1 / Service2 / ...
- 인터페이스로 경계가 나뉘어 있다면, Service가 바뀌어도 바로 Client에 영향을 주지 않으며, 인터페이스의 변화에만 영향을 받음.
📌 마무리
- 아키텍처 경계를 설정하는 방법에는 수준별로 더 많은 전략이 있음.
- 완전한 경계 vs 현실적인 비용 간의 균형이 중요함.
- 언제, 어디에, 얼마나 강한 경계를 설정할지 결정하는 것이 아키텍트의 핵심 역할임.
※ 본 글은 『Clean Architecture』(로버트 C. 마틴 저) 5부 24장을 기반으로 학습 목적으로 요약한 글입니다.
※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.
'아키텍처' 카테고리의 다른 글
Clean Architecture 정리 - 5부 26장 메인 컴포넌트 (1) | 2025.07.26 |
---|---|
Clean Architecture 정리 - 5부 25장 계층과 경계 (5) | 2025.07.26 |
Clean Architecture 정리 - 5부 23장 프레젠터와 험블객체 (1) | 2025.07.25 |
Clean Architecture 정리 - 5부 22장 클린아키텍처 (0) | 2025.07.24 |
Clean Architecture 정리 - 5부 21장 소리치는 아키텍처 (0) | 2025.07.24 |