본문 바로가기
도메인 주도 설계

adapter 패턴, 이렇게도 쓰일수 있구나(with IDDD)

by simplify-len 2020. 8. 23.

디자인 패턴의 착각 중에 모든 코드는 디자인 패턴에 나온 코드가 마치 책에서 나온 내용처럼 나올 것이라는 부분에 존재합니다.

오늘도, 다시한번 위와 같은 디자인 패턴의 착각을 느낍니다. 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;
    }
}

 

댓글