Distribution & Base Patterns — 실전 예시와 템플릿
예시: LLM 연동을 Gateway + DTO로 설계
Academic Life System에서 외부 LLM API와의 통신이 가장 분산 패턴에 가까운 부분이다.
Gateway 패턴 — 외부 시스템 격리
// Gateway — 외부 시스템 접근을 캡슐화
class OpenAIGateway implements LLMGateway:
sendPrompt(context: CounselingContext) -> LLMResponse:
// 1. DTO를 API 요청 형식으로 변환
requestBody = {
"model": "gpt-4",
"messages": [
{"role": "system", "content": buildSystemPrompt()},
{"role": "user", "content": context.toPromptString()}
]
}
// 2. HTTP 호출
httpResponse = http.post(endpoint, requestBody, headers={"Authorization": apiKey})
// 3. API 응답을 DTO로 변환
return LLMResponse(
recommendation = httpResponse.choices[0].message.content,
confidence = extractConfidence(httpResponse),
reasoning = extractReasoning(httpResponse)
)
// 테스트용 Gateway
class MockLLMGateway implements LLMGateway:
sendPrompt(context) -> LLMResponse:
return LLMResponse(
recommendation = "CS101을 먼저 수강하세요.",
confidence = 0.9,
reasoning = "선수과목 분석 결과"
)
Remote Facade + DTO — API 서빙 시
만약 프론트엔드가 SPA(React 등)라서 REST API로 소통한다면:
// Remote Facade — 여러 도메인 객체의 데이터를 하나의 DTO로 조합
class RecommendationFacade:
getRecommendations(studentId, semester) -> RecommendationDTO:
// 여러 도메인 객체에서 데이터 수집
student = Student.find(studentId)
recommendations = student.getRecommendations(semester)
// 하나의 DTO로 조합하여 반환 (네트워크 호출 1회로 끝)
return RecommendationDTO(
studentName = student.name,
semester = semester,
courses = recommendations.map(c -> CourseDTO(
courseId = c.courseId,
name = c.name,
credits = c.credits,
reason = c.recommendationReason,
priority = c.priority
)),
totalCredits = recommendations.sum(c -> c.credits)
)
Registry 패턴 — Mapper/Gateway 찾기
class MapperRegistry:
// Singleton
static instance: MapperRegistry
mappers: Map<Class, Mapper>
gateways: Map<String, Gateway>
static getMapper(cls) -> Mapper:
return instance.mappers.get(cls)
static getGateway(name) -> Gateway:
return instance.gateways.get(name)
// 초기화 (앱 시작 시)
static init():
instance = new MapperRegistry()
instance.mappers[Student] = new StudentMapper()
instance.mappers[Course] = new CourseMapper()
instance.gateways["llm"] = new OpenAIGateway(config.apiKey)
Plugin 패턴 — 런타임 교체
// 설정 파일로 구현체 결정
config.yaml:
llm_gateway: "openai" # 또는 "local" 또는 "mock"
db_type: "sqlite" # 또는 "mysql"
// Plugin 로더
class PluginLoader:
static loadLLMGateway(config) -> LLMGateway:
switch config.llm_gateway:
case "openai": return new OpenAIGateway(config.api_key)
case "local": return new LocalLLMGateway(config.model_path)
case "mock": return new MockLLMGateway()
템플릿
Gateway 설계 템플릿
// Gateway 인터페이스
interface [외부시스템]Gateway:
[동작]([입력DTO]) -> [출력DTO]
isAvailable() -> bool
// 실제 구현
class [구체구현]Gateway implements [외부시스템]Gateway:
[동작]([입력DTO]) -> [출력DTO]:
// 1. DTO → 외부 시스템 요청 형식 변환
request = convertToExternalFormat([입력DTO])
// 2. 외부 시스템 호출
rawResponse = [외부호출](request)
// 3. 응답 → DTO 변환
return convertToDTO(rawResponse)
// 테스트용
class Mock[외부시스템]Gateway implements [외부시스템]Gateway:
[동작]([입력DTO]) -> [출력DTO]:
return [고정된 응답]
DTO 설계 템플릿
// DTO 설계 원칙:
// 1. 비즈니스 로직 없음 — 순수 데이터 컨테이너
// 2. 직렬화 가능 (JSON/XML)
// 3. 여러 도메인 객체의 데이터를 조합 가능
class [기능명]DTO:
// 기본 정보
[field1]: [Type]
[field2]: [Type]
// 관련 객체 정보 (중첩 DTO)
[related]: [관련DTO]
[relatedList]: List<[관련DTO]>
// 파생 값 (클라이언트 편의)
[computed]: [Type]
// DTO ↔ JSON 변환
toJSON() -> String
static fromJSON(json) -> [기능명]DTO
Remote Facade 템플릿
// Remote Facade — 도메인 객체의 fine-grained 인터페이스를
// coarse-grained 인터페이스로 감싼다
class [기능영역]Facade:
// 하나의 메서드 = 하나의 API 호출로 완결
[유스케이스]([최소한의 입력]) -> [포괄적DTO]:
// 1. 여러 도메인 객체 조회
[obj1] = [mapper1].find([id1])
[obj2] = [mapper2].findBy[조건]([조건])
// 2. 도메인 로직 실행
[result] = [obj1].[비즈니스메서드]([obj2])
// 3. 결과를 DTO로 조합
return [포괄적DTO](
[obj1에서 가져온 필드들],
[obj2에서 가져온 필드들],
[result에서 가져온 필드들]
)
외부 시스템 연동 체크리스트
## [프로젝트명] 외부 시스템 연동
### 연동 대상
| 외부 시스템 | Gateway 이름 | 구현체 | Mock 여부 |
|-----------|------------|--------|----------|
| _________ | _________Gateway | _________ | [ ] Mock 준비됨 |
| _________ | _________Gateway | _________ | [ ] Mock 준비됨 |
### 격리 확인
- [ ] 도메인 클래스에서 외부 시스템을 직접 호출하지 않는가?
- [ ] Gateway 인터페이스로 추상화되어 있는가?
- [ ] Mock Gateway로 교체하여 테스트할 수 있는가?
- [ ] 외부 시스템 장애 시 graceful degradation이 가능한가?
Remote Facade — Fine vs Coarse-Grained 비교
DTO Assembler 패턴
DTO 조립 로직을 별도 클래스로 분리:
Separated Interface — 의존성 역전의 핵심
인터페이스는 Domain에, 구현은 Infrastructure에. Domain은 외부를 모른다.
Layer Supertype — C++ 구현
// 모든 도메인 객체의 부모
class DomainObject {
protected:
string id;
int version; // Optimistic Lock용
Timestamp modified;
string modifiedBy;
public:
string getId() { return id; }
int getVersion() { return version; }
bool isNew() { return id == ""; }
};
// 모든 Mapper의 부모
class AbstractMapper {
protected:
string tableName;
vector<string> columns;
string buildSelectSQL(string whereClause) {
return "SELECT " + join(columns, ",") + " FROM " + tableName
+ " WHERE " + whereClause;
}
string buildUpdateSQL() {
auto sets = columns.map(c -> c + "=?");
return "UPDATE " + tableName + " SET " + join(sets, ",")
+ ", version=version+1 WHERE id=? AND version=?";
}
void checkConcurrency(int rowCount, string id) {
if (rowCount == 0)
throw ConcurrencyException("Conflict on " + tableName + " id=" + id);
}
};
Service Stub — 테스트용 외부 서비스 대체
class LLMServiceStub : public LLMPort {
map<string, string> cannedResponses;
string complete(string prompt) override {
for (auto& [keyword, response] : cannedResponses) {
if (prompt.contains(keyword)) return response;
}
return "Default stub response";
}
};
// 테스트에서 사용
void testRecommendation() {
LLMServiceStub stub;
stub.cannedResponses["recommend"] = "CS101, CS201, CS301";
RecommendationService service(stub);
auto result = service.recommend(testStudent);
assert(result.size() == 3);
}
Value Object — 불변 값 객체
class Money {
double amount;
string currency;
bool operator==(const Money& other) const {
return amount == other.amount && currency == other.currency;
}
Money add(const Money& other) const {
assert(currency == other.currency);
return Money(amount + other.amount, currency);
}
};
class Semester {
int year;
string term; // "spring", "fall"
bool operator==(const Semester& other) const {
return year == other.year && term == other.term;
}
Semester next() {
if (term == "spring") return Semester(year, "fall");
return Semester(year + 1, "spring");
}
};
Base Pattern 적용 체크리스트
## [프로젝트명] Base Pattern 적용 현황
| 패턴 | 적용 여부 | 구현 위치 | 비고 |
|------|----------|----------|------|
| Gateway | | | 외부 시스템(LLM, 크롤러 등) |
| Mapper | | | Data Mapper를 쓰면 자동 |
| Layer Supertype | | | DomainObject, AbstractMapper |
| Separated Interface | | | 인터페이스 위치 = Domain |
| Registry | | | Mapper 조회용 |
| Value Object | | | Money, Semester 등 |
| Plugin | | | 설정으로 구현체 결정 |
| Service Stub | | | 테스트용 |
| Record Set | | | Table Module 사용 시 |