디자인 패턴의 착각 중에 모든 코드는 디자인 패턴에 나온 코드가 마치 책에서 나온 내용처럼 나올 것이라는 부분에 존재합니다.
오늘도, 다시한번 위와 같은 디자인 패턴의 착각을 느낍니다. IDDD_sample 코드 중, 서비스 단에서 팩토리의 사용 예를 설명하는 부분에서 흥미로운 부분을 발견했습니다.
레이어드 아키텍처에서 TranslatingCollaboratorService.java 의 존재는 이름과 같이 Collaborator 라는 협력 객체를 해석시켜주는 역할을 하는 클래스입니다. 어떻게 해석할 수 있는가에 대한 질문은, 이 글에서 논외이기 때문에 제외하겠습니다.
우리가 흔히 아는 adapter 패턴이 사용하는 client입장에서는 factory로서 동작할 수 있다는 것이 이 글의 요지입니다.
그럼 가장 핵심이 되는 UserInRoleAdapter.java 부터 보시면 아래 코드와 같습니다.
package com.saasovation.collaboration.port.adapter.service;
import com.saasovation.collaboration.domain.model.collaborator.Collaborator;
import com.saasovation.collaboration.domain.model.tenant.Tenant;
public interface UserInRoleAdapter {
<T extends Collaborator> T toCollaborator(
Tenant aTenant,
String anIdentity,
String aRoleName,
Class<T> aCollaboratorClass);
}
해당 API는 Adapter를 통해 Collaborator를 변환시킵니다.
어떻게일까요? 여기서 또다른 객체가 출연합니다.
바로 CollaboratorService.java 입니다.
public interface CollaboratorService {
Author authorFrom(Tenant aTenant, String anIdentity);
Creator creatorFrom(Tenant aTenant, String anIdentity);
Moderator moderatorFrom(Tenant aTenant, String anIdentity);
Owner ownerFrom(Tenant aTenant, String anIdentity);
Participant participantFrom(Tenant aTenant, String anIdentity);
}
UserInRoleAdapter.java 를 활용해 각각의 author, creator, moderator, owner, participant 등을 변환시켜 줄 것입니다.
바로 아래와 같은 코드를 통해서입니다.
public class TranslatingCollaboratorService implements CollaboratorService {
private final UserInRoleAdapter userInRoleAdapter;
public TranslatingCollaboratorService(UserInRoleAdapter aUserInRoleAdapter) {
super();
this.userInRoleAdapter = aUserInRoleAdapter;
}
@Override
public Author authorFrom(Tenant aTenant, String anIdentity) {
Author author =
this.userInRoleAdapter()
.toCollaborator(
aTenant,
anIdentity,
"Author",
Author.class);
return author;
}
@Override
public Creator creatorFrom(Tenant aTenant, String anIdentity) {
Creator creator =
this.userInRoleAdapter()
.toCollaborator(
aTenant,
anIdentity,
"Creator",
Creator.class);
return creator;
}
@Override
public Moderator moderatorFrom(Tenant aTenant, String anIdentity) {
Moderator moderator =
this.userInRoleAdapter()
.toCollaborator(
aTenant,
anIdentity,
"Moderator",
Moderator.class);
return moderator;
}
@Override
public Owner ownerFrom(Tenant aTenant, String anIdentity) {
Owner owner =
this.userInRoleAdapter()
.toCollaborator(
aTenant,
anIdentity,
"Owner",
Owner.class);
return owner;
}
@Override
public Participant participantFrom(Tenant aTenant, String anIdentity) {
Participant participant =
this.userInRoleAdapter()
.toCollaborator(
aTenant,
anIdentity,
"Participant",
Participant.class);
return participant;
}
private UserInRoleAdapter userInRoleAdapter() {
return this.userInRoleAdapter;
}
}
여기에 아직 등장하지 않은 부분이 있습니다. userInRoleAdapter.java 의 구현 부분이 아직 드러나지 않았습니다. 이 부분을 설명하는 것을 또 해당 글의 주제에서 벗어나는 이야기입니다만, 궁금하신 분들을 위해 아래에 추가 내용을 적어보겠습니다.
UserInRoleAdapter를 통해 각각의 Collaborator가 변환됨을 이해할 수 있습니다. 즉, UserInRoleAdapter는 팩토리 역할을 하는 것을 이해할 수 있었습니다.
---
추가 내용.
UserInRoleAdapter 은 말 그대로 변환만을 담당하는 객체입니다. 실질적인 구현 부분은 어디에 존재할까요?
당연히 Interface 인 UserInRoleAdapter의 확장된 객체라고 생각할 수 있지만, Adapter는 Adapter일 뿐, 한 가지 클래스에는 한 가지 역할만 해야한다는 관점에서는 이곳에 변환되는 비지니스 로직이 들어가서는 안됩니다. 본연의 Adapter 역할만 충실해야 합니다.
UserInRoleAdapter의 확장된 객체 HttpUserInRoleAdapter.java 도 그 역할을 충실히 이행하고 있습니다.
public class HttpUserInRoleAdapter implements UserInRoleAdapter {
private static final String HOST = "localhost";
private static final String PORT = "8081";
private static final String PROTOCOL = "http";
private static final String URL_TEMPLATE =
"/idovation/tenants/{tenantId}/users/{username}/inRole/{role}";
public HttpUserInRoleAdapter() {
super();
}
public <T extends Collaborator> T toCollaborator(
Tenant aTenant,
String anIdentity,
String aRoleName,
Class<T> aCollaboratorClass) {
T collaborator = null;
try {
ClientRequest request =
this.buildRequest(aTenant, anIdentity, aRoleName);
ClientResponse<String> response = request.get(String.class);
if (response.getStatus() == 200) {
collaborator =
new CollaboratorTranslator()
.toCollaboratorFromRepresentation(
response.getEntity(),
aCollaboratorClass);
} else if (response.getStatus() == 204) {
; // not an error, return null
} else {
throw new IllegalStateException(
"There was a problem requesting the user: "
+ anIdentity
+ " in role: "
+ aRoleName
+ " with resulting status: "
+ response.getStatus());
}
} catch (Throwable t) {
throw new IllegalStateException(
"Failed because: " + t.getMessage(), t);
}
return collaborator;
}
...
}
위 코드를 살펴보면, toCollaborator() 의 코드안에는 어떠한 변환 로직이 존재하지 않습니다. 직접적으로 Collaborator를 생성하는 로직을 말합니다. 만약 변환시 로직이 있어야 한다면 아마도 이런 코드가 존재하겠죠?
if (aCollaboratorClass instanceOf Creator){
//Something .. for Creator
} else if (aCollaboratorClass instanceOf Owner) {
//Something To do for owner
} else if (...){
...
}
이런 구체적인 부분에 대한 코드는 또다른 객체에게 위임합니다. 이미 눈치 채신분이 많이 계실거라 생각되는데 CollaboratorTranslator.java 라는 객체가 이를 담당합니다.
코드가 좀 길 수 있지만, 첨부하는 이유는 좀 더 명확하게 설명하기 위해서입니다.
import java.lang.reflect.Constructor;
import com.saasovation.collaboration.domain.model.collaborator.Collaborator;
import com.saasovation.common.media.RepresentationReader;
public class CollaboratorTranslator {
public CollaboratorTranslator() {
super();
}
public <T extends Collaborator> T toCollaboratorFromRepresentation(
String aUserInRoleRepresentation,
Class<T> aCollaboratorClass)
throws Exception {
RepresentationReader reader =
new RepresentationReader(aUserInRoleRepresentation);
String username = reader.stringValue("username");
String firstName = reader.stringValue("firstName");
String lastName = reader.stringValue("lastName");
String emailAddress = reader.stringValue("emailAddress");
T collaborator =
this.newCollaborator(
username,
firstName,
lastName,
emailAddress,
aCollaboratorClass);
return collaborator;
}
private <T extends Collaborator> T newCollaborator(
String aUsername,
String aFirstName,
String aLastName,
String aEmailAddress,
Class<T> aCollaboratorClass)
throws Exception {
Constructor<T> ctor =
aCollaboratorClass.getConstructor(
String.class, String.class, String.class);
T collaborator =
ctor.newInstance(
aUsername,
(aFirstName + " " + aLastName).trim(),
aEmailAddress);
return collaborator;
}
}
'도메인 주도 설계' 카테고리의 다른 글
반버논이 말하는 Repository 란? (0) | 2020.09.15 |
---|---|
핵사고날 아키텍처에서 Port(Adapter)의 의미는 무엇일까? (1) | 2020.08.29 |
왜 DDD 에서 팩토리 패턴을 사용하는 걸까? (0) | 2020.08.22 |
[동영상 정리]애플리케이션 아키텍처와 객체지향 - 조영호님 (0) | 2020.08.21 |
반버논이 말하는 Value Object 란? (0) | 2020.08.12 |
댓글