Clean Architecture : 발표자를 포함하거나 데이터를 반환하는 사용 사례?


42

청소 아키텍처는 응답 / 디스플레이를 처리합니다 (DIP 다음, 주입) 발표자의 실제 구현을 호출 인터랙 사용 사례를 수 있도록 제안합니다. 그러나 사람들 이이 아키텍처를 구현하고 상호 작용기의 출력 데이터를 반환 한 다음 컨트롤러 (어댑터 계층)에서 처리 방법을 결정하게합니다. 두 번째 솔루션은 인터랙 터에 대한 입력 및 출력 포트를 명확하게 정의하지 않고 애플리케이션 계층에서 애플리케이션 책임을 유출합니까?

입력 및 출력 포트

Clean Architecture 정의, 특히 컨트롤러, 유스 케이스 인터랙 터 및 발표자 간의 관계를 설명하는 작은 흐름도를 고려할 "유스 케이스 출력 포트"가 무엇인지 올바르게 이해하고 있는지 확실하지 않습니다.

육각형 아키텍처와 같은 클린 아키텍처는 1 차 포트 (방법)와 2 차 포트 (어댑터가 구현할 인터페이스)를 구분합니다. 통신 흐름에 따라 "유스 케이스 입력 포트"가 기본 포트 (따라서 메소드)이고 "유스 케이스 출력 포트"인터페이스가 구현 될 것으로 예상됩니다. 실제 어댑터를 사용하는 생성자 인수 인터랙 터가 사용할 수 있도록

코드 예

코드 예제를 작성하려면 컨트롤러 코드 일 수 있습니다.

Presenter presenter = new Presenter();
Repository repository = new Repository();
UseCase useCase = new UseCase(presenter, repository);
useCase->doSomething();

발표자 인터페이스 :

// Use Case Output Port
interface Presenter
{
    public void present(Data data);
}

마지막으로 인터랙 터 자체 :

class UseCase
{
    private Repository repository;
    private Presenter presenter;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.repository = repository;
        this.presenter = presenter;
    }

    // Use Case Input Port
    public void doSomething()
    {
        Data data = this.repository.getData();
        this.presenter.present(data);
    }
}

발표자를 호출하는 인터랙 터에서

앞의 해석은 앞서 언급 한 다이어그램 자체에 의해 확인 된 것으로 보입니다. 여기서 컨트롤러와 입력 포트 사이의 관계는 "날카로운"헤드 ( "연결"에 대한 UML, "가 있음"을 의미 함)가있는 실선 화살표로 표시됩니다. 발표자와 출력 포트 사이의 관계는 "백색"헤드 ( "상속"에 대한 UML, "구현"에 대한 것이 아닌 UM)가있는 실선 화살표로 표시됩니다. 그것은 어쨌든 의미입니다).

또한 다른 질문에 대한이 답변 에서 Robert Martin은 상호 작용자가 읽기 요청에 따라 발표자를 호출하는 유스 케이스를 정확하게 설명합니다.

맵을 클릭하면 placePinController가 호출됩니다. 클릭의 위치 및 기타 상황에 맞는 데이터를 수집하고 placePinRequest 데이터 구조를 생성 한 후이를 Pin의 위치를 ​​확인하고 필요한 경우 유효성을 검사하며 Pin을 기록 할 Place 엔티티를 작성하여 EditPlaceReponse를 구성하는 PlacePinInteractor에 전달합니다. 장소 편집기 화면을 표시하는 EditPlacePresenter로 전달합니다.

MVC 에서이 기능을 제대로 수행하기 위해 전통적으로 컨트롤러로 들어가는 응용 프로그램 논리가 상호 작용기로 이동한다고 생각할 수 있습니다. 응용 프로그램 논리가 응용 프로그램 계층 외부로 누출되는 것을 원하지 않기 때문입니다. 어댑터 계층의 컨트롤러는 인터랙 터를 호출하고 프로세스에서 약간의 데이터 형식 변환을 수행합니다.

이 계층의 소프트웨어는 데이터를 사용 사례 및 엔터티에 가장 편리한 형식에서 데이터베이스 또는 웹과 같은 일부 외부 기관에 가장 편리한 형식으로 변환하는 어댑터 집합입니다.

원래 기사에서 인터페이스 어댑터에 대해 이야기했습니다.

인터랙 터에서 데이터를 반환

그러나이 접근법에 대한 나의 문제는 유스 케이스가 프레젠테이션 자체를 처리해야한다는 것입니다. 이제 Presenter인터페이스 의 목적 은 여러 다른 유형의 발표자 (GUI, 웹, CLI 등)를 표현하기에 충분히 추상적이며 실제로는 "출력"을 의미하며, 이는 유스 케이스 일 수 있습니다. 매우 잘하지만 여전히 그것에 대해 완전히 확신하지는 못합니다.

이제 깨끗한 아키텍처의 응용 프로그램을 웹에서 살펴보면 출력 포트를 일부 DTO를 반환하는 방법으로 해석하는 사람들 만 찾는 것 같습니다. 이것은 다음과 같습니다.

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);

// I'm omitting the changes to the classes, which are fairly obvious

유스 케이스에서 프리젠 테이션을 "호출"하는 책임을 옮기고 있기 때문에 유스 케이스는 더 이상 데이터를 제공하는 것이 아니라 데이터로 무엇을해야하는지 알 필요가 없습니다. 또한이 경우에도 유스 케이스는 여전히 외부 레이어에 대해 아무것도 모르기 때문에 종속성 규칙을 위반하지 않습니다.

그러나 유스 케이스는 실제 프리젠 테이션이 더 이상 수행되는 순간을 제어하지 않습니다 (예 : 로깅과 같이 해당 시점에 추가 작업을 수행하거나 필요한 경우 모두 중단하는 데 유용 할 수 있음). 또한 컨트롤러가 getData()새로운 출력 포트 인 방법 만 사용하므로 유스 케이스 입력 포트를 잃어 버렸습니다 . 또한, 우리는 여기에서 "말하고 묻지 말아라"라는 원칙을 어 기고 있다고 생각합니다. 왜냐하면 우리는 인터랙 터에게 실제 데이터를 처리하도록 지시하기보다는 어떤 데이터를 처리하도록 요구하기 때문입니다. 처음.

요점

그렇다면이 두 가지 대안 중 어느 것이 클린 아키텍처에 따른 유스 케이스 출력 포트의 "올바른"해석입니까? 둘 다 가능합니까?


3
교차 게시는 권장하지 않습니다. 여기에 질문을 게시하려면 스택 오버플로에서 질문을 삭제해야합니다.
Robert Harvey

답변:


48

Clean Architecture는 유스 케이스 인터랙 터가 응답자 / 디스플레이를 처리하기 위해 발표자의 실제 구현 (DIP에 따라 주입 됨)을 호출하도록 제안합니다. 그러나 사람들 이이 아키텍처를 구현하고 상호 작용기의 출력 데이터를 반환 한 다음 컨트롤러 (어댑터 계층)에서 처리 방법을 결정하게합니다.

그것은 Clean , Onion 또는 Hexagonal Architecture가 아닙니다 . 이것이 이것입니다 :

여기에 이미지 설명을 입력하십시오

하지 않는 것이 MVC는 그런 식으로 할 수있다

여기에 이미지 설명을 입력하십시오

여러 가지 방법을 사용하여 모듈간에 통신하고 MVC라고 부를 수 있습니다. MVC를 사용한다고 말하는 것이 실제로 구성 요소가 어떻게 통신하는지 알려주지는 않습니다. 표준화되지 않았습니다. 단지 세 가지 책임에 중점을 둔 구성 요소가 적어도 세 개 있다는 것입니다.

이러한 방법 중 일부에는 다른 이름 이 지정되었습니다 . 여기에 이미지 설명을 입력하십시오

그리고 이들 모두를 MVC라고 할 수 있습니다.

어쨌든, 그 중 어느 것도 유행어 아키텍처 (Clean, Onion 및 Hex)가 당신에게 요구하는 것을 실제로 포착하지 못합니다.

여기에 이미지 설명을 입력하십시오

떨어진 주위되는 데이터 구조를 추가 (거꾸로 어떤 이유로 그것을 플립) 그리고 당신이 얻을 :

여기에 이미지 설명을 입력하십시오

여기서 분명한 것은 응답 모델이 컨트롤러를 통해 행진하지 않는다는 것입니다.

당신이 독수리 눈이라면, 유행어 아키텍처 만이 순환 의존성을 완전히 피한다는 것을 알았을 것입니다 . 중요한 것은 구성 요소를 순환하면서 코드 변경의 영향이 확산되지 않음을 의미합니다. 신경 쓰지 않는 코드에 도달하면 변경이 중지됩니다.

제어 흐름이 시계 방향으로 진행되도록 거꾸로 뒤집 었는지 궁금합니다. 이에 대한 자세한 내용은이 "백색"화살표 머리입니다.

두 번째 솔루션은 인터랙 터에 대한 입력 및 출력 포트를 명확하게 정의하지 않고 애플리케이션 계층에서 애플리케이션 책임을 유출합니까?

Controller에서 Presenter 로의 통신은 응용 프로그램 "계층"을 통과하기위한 것이므로 Controller가 Presenters 작업의 일부가되도록하는 것은 누출 가능성이 있습니다. 이것이 VIPER 아키텍처에 대한 나의 주요한 비판입니다 .

이것들을 분리하는 것이 중요한 이유는 커맨드 쿼리 책임 분리를 연구함으로써 가장 잘 이해 될 수있을 것입니다 .

입력 및 출력 포트

Clean Architecture 정의, 특히 컨트롤러, 유스 케이스 인터랙 터 및 발표자 간의 관계를 설명하는 작은 흐름도를 고려할 때 "유스 케이스 출력 포트"가 무엇인지 올바르게 이해하고 있는지 확실하지 않습니다.

이 특정 사용 사례에 대해 출력을 보내는 API입니다. 그 이상입니다. 이 사용 사례의 인터랙 터는 출력이 GUI, CLI, 로그 또는 오디오 스피커로 전달되는지 알 필요도없고 알 필요도 없습니다. 인터랙 터가 알아야 할 모든 것은 작업 결과를보고 할 수있는 가장 간단한 API입니다.

육각형 아키텍처와 같은 클린 아키텍처는 1 차 포트 (방법)와 2 차 포트 (어댑터가 구현할 인터페이스)를 구분합니다. 통신 흐름에 따라 "유스 케이스 입력 포트"가 기본 포트 (따라서 메소드)이고 "유스 케이스 출력 포트"인터페이스가 구현 될 것으로 예상됩니다. 실제 어댑터를 사용하는 생성자 인수 인터랙 터가 사용할 수 있도록

출력 포트가 입력 포트와 다른 이유는 추상화 된 계층에 의해 소유되지 않아야하기 때문입니다. 즉, 추상화하는 레이어에 변경 사항을 지시해서는 안됩니다. 응용 프로그램 계층과 작성자 만 출력 포트를 변경할 수 있는지 결정해야합니다.

이것은 추상화하는 레이어가 소유 한 입력 포트와 대조적입니다. 응용 프로그램 계층 작성자 만 입력 포트의 변경 여부를 결정해야합니다.

이러한 규칙을 따르면 응용 프로그램 계층 또는 내부 계층이 외부 계층에 대해 전혀 알지 못한다는 생각이 유지됩니다.


발표자를 호출하는 인터랙 터에서

앞의 해석은 앞서 언급 한 다이어그램 자체에 의해 확인 된 것으로 보입니다. 여기서 컨트롤러와 입력 포트 사이의 관계는 "날카로운"헤드 ( "연결"에 대한 UML, "가 있음"을 의미 함)가있는 실선 화살표로 표시됩니다. 발표자와 출력 포트 사이의 관계는 "백색"헤드 ( "상속"에 대한 UML, "구현"에 대한 것이 아닌 UM)가있는 실선 화살표로 표시됩니다. 그것은 어쨌든 의미입니다).

"백색"화살표의 중요한 점은 다음과 같이 할 수 있다는 것입니다.

여기에 이미지 설명을 입력하십시오

제어 흐름이 종속성의 반대 방향으로 진행되도록 할 수 있습니다! 즉, 내부 레이어는 외부 레이어에 대해 알 필요가 없지만 내부 레이어로 뛰어 들어 다시 돌아올 수 있습니다!

"interface"키워드를 사용하는 것과는 아무런 관련이 없습니다. 추상 클래스 로이 작업을 수행 할 수 있습니다. 확장 가능한 한 (ick) 구체적인 클래스로 할 수 있습니다. Presenter가 구현해야하는 API 정의에만 중점을 둔 작업으로 수행하는 것이 좋습니다. 열린 화살표는 다형성만을 요구합니다. 어떤 종류의 당신에게 달려 있습니다.

의존성 역전 원리를 연구함으로써 그러한 의존성의 방향을 뒤집는 것이 중요한 이유를 알 수 있습니다 . 나는이 다이어그램에 그 원리를 매핑 여기 .

인터랙 터에서 데이터를 반환

그러나이 접근법에 대한 나의 문제는 유스 케이스가 프레젠테이션 자체를 처리해야한다는 것입니다. 이제 Presenter 인터페이스의 목적은 여러 다른 유형의 발표자 (GUI, 웹, CLI 등)를 표현하기에 충분히 추상적이며 실제로는 "출력"을 의미하며, 이는 유스 케이스입니다. 잘했을지도 모르지만 여전히 그것에 대해 완전히 확신하지는 못합니다.

아니, 그게 다야. 내부 레이어가 외부 레이어에 대해 알지 못하게하는 요점은 외부 레이어를 제거, 교체 또는 리팩터링 할 수 있다는 것입니다. 그들이 모르는 것이 그들을 해치지 않을 것입니다. 그렇게 할 수 있다면 바깥 쪽을 원하는대로 바꿀 수 있습니다.

이제 깨끗한 아키텍처의 응용 프로그램을 웹에서 살펴보면 출력 포트를 일부 DTO를 반환하는 방법으로 해석하는 사람들 만 찾는 것 같습니다. 이것은 다음과 같습니다.

Repository repository = new Repository();
UseCase useCase = new UseCase(repository);
Data data = useCase.getData();
Presenter presenter = new Presenter();
presenter.present(data);
// I'm omitting the changes to the classes, which are fairly obvious

유스 케이스에서 프리젠 테이션을 "호출"하는 책임을 옮기고 있기 때문에 유스 케이스는 더 이상 데이터를 제공하는 것이 아니라 데이터로 무엇을해야하는지 알 필요가 없습니다. 또한이 경우에도 유스 케이스는 여전히 외부 레이어에 대해 아무것도 모르기 때문에 종속성 규칙을 위반하지 않습니다.

여기서 문제는 이제 데이터를 요청하는 방법도 데이터를 받아들이는 것이어야한다는 것입니다. Controller가 Usecase Interactor를 호출하기 전에 응답 모델의 모양, 위치 및 표시 방법을 알지 못합니다.

다시 한 번, Command Query Responsibility Segregation 을 연구 하여 이것이 왜 중요한지 알아보십시오.

그러나 유스 케이스는 실제 프리젠 테이션이 더 이상 수행되는 순간을 제어하지 않습니다 (예 : 로깅과 같이 해당 시점에 추가 작업을 수행하거나 필요한 경우 모두 중단하는 데 유용 할 수 있음). 또한 컨트롤러가 getData () 메소드 (새로운 출력 포트) 만 사용하므로 유스 케이스 입력 포트를 잃어 버렸습니다. 또한, 우리는 여기에서 "말하고 묻지 말아라"라는 원칙을 어 기고 있다고 생각합니다. 처음.

예! 묻지 않고 말하는 것은이 객체가 절차적인 것이 아니라 방향을 유지하는 데 도움이됩니다.

요점

그렇다면이 두 가지 대안 중 어느 것이 클린 아키텍처에 따른 유스 케이스 출력 포트의 "올바른"해석입니까? 둘 다 가능합니까?

작동하는 모든 것이 가능합니다. 그러나 나는 당신이 제시 한 두 번째 옵션이 Clean Architecture를 충실히 따른다고 말하고 싶지 않습니다. 작동하는 것일 수 있습니다. 그러나 Clean Architecture가 요구하는 것은 아닙니다.


4
시간을내어 깊이있는 설명을 작성해 주셔서 감사합니다.
swahnee

1
Clean Architecture 주위에 머리를 감 으려고 노력했지만이 답변은 환상적인 리소스였습니다. 잘 했어요!
Nathan

훌륭하고 자세하게 답변합니다. 감사합니다. UseCase를 실행하는 동안 GUI 업데이트 (예 : 큰 파일을 업로드하는 동안 진행률 표시 줄 업데이트)에 대한 몇 가지 팁 (또는 설명)을 알려 주시겠습니까?
Ewoks

1
@Ewoks, 질문에 대한 빠른 답변으로 Observable 패턴을 조사해야합니다. 사용 사례에서 주제를 반환하고 진행 상황 업데이트를 주제에 알릴 수 있습니다. 발표자는 주제를 구독하고 알림에 응답합니다.
Nathan

7

귀하의 질문과 관련된 토론에서 Bob 아저씨는 Clean Architecture에서 발표자의 목적을 설명합니다.

이 코드 샘플은 다음과 같습니다.

namespace Some\Controller;

class UserController extends Controller {
    public function registerAction() {
        // Build the Request object
        $request = new RegisterRequest();
        $request->name = $this->getRequest()->get('username');
        $request->pass = $this->getRequest()->get('password');

        // Build the Interactor
        $usecase = new RegisterUser();

        // Execute the Interactors method and retrieve the response
        $response = $usecase->register($request);

        // Pass the result to the view
        $this->render(
            '/user/registration/template.html.twig', 
            array('id' =>  $response->getId()
        );
    }
}

밥 아저씨는 이렇게 말했습니다.

" 발표자의 목적은 사용 사례를 UI 형식에서 분리하는 것입니다. 예를 들어 $ response 변수는 인터랙 터에 의해 작성되지만 뷰에서 사용됩니다. 이는 인터랙 터를 뷰에 결합합니다. $ response 객체의 필드 중 하나가 날짜라고 가정하면이 필드는 다양한 날짜 형식으로 렌더링 될 수있는 이진 날짜 객체가 될 수 있습니다. DD / MM / YYYY와 같은 매우 구체적인 날짜 형식이 필요합니다. 누구의 책임은 그것이 인터랙 그 형식을 만드는 경우, 그것은 너무 많이보기에 대해 알고있다. 그러나보기는 다음 바이너리 날짜 객체를 걸리는 경우가 인터랙에 대해 너무 많이 알고? 형식을 만드는 것입니다.

"발표자의 작업이 걸릴 것입니다 응답 오브젝트의 데이터를보고보기에 맞게 형식화하십시오. 뷰나 인터랙 터는 서로의 형식에 대해 알지 못합니다. "

--- 밥 아저씨

(업데이트 : 2019 년 5 월 31 일)

밥 삼촌의 대답을 감안할 때, 우리가 옵션 # 1 을 수행하는지 (상호 작용자가 발표자를 사용하게 하는지) 중요하지 않다고 생각 합니다 ...

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... 또는 우리는 옵션 # 2를 수행합니다 (인터랙 터가 응답을 반환하고 컨트롤러 내에 발표자를 만든 다음 응답을 발표자에게 전달합니다) ...

class Controller
{
    public void ExecuteUseCase(Data data)
    {
        Request request = ...
        UseCase useCase = new UseCase(repository);
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        presenter.Show(response);
    }
}

개인적으로, 나는 옵션 # 1을 선호 내가 할 수 제어되고 싶어하기 때문에 내부interactor 아래의 예와 같이, 데이터와 오류 메시지를 표시하기 :

class UseCase
{
    private Presenter presenter;
    private Repository repository;

    public UseCase(Repository repository, Presenter presenter)
    {
        this.presenter = presenter;
        this.repository = repository;
    }

    public void Execute(Request request)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {...}
        this.presenter.Show(response);
    }
}

... 인터랙 터 외부가 아닌 if/else내부 프레젠테이션과 관련된 작업을 수행 할 수 있기를 원합니다 interactor.

반면에 우리가 옵션 # 2를 할 경우, 우리는에 오류 메시지 (들)을 저장하는 것 response, 객체를 돌려 response으로부터 개체를 interactor받는 사람 controller, 그리고 수 있도록 controller 구문 분석을response 개체를 ...

class UseCase
{
    public Response Execute(Request request)
    {
        Response response = new Response();
        if (<invalid request>) 
        {
            response.AddError("...");
        }

        if (<there is another error>) 
        {
            response.AddError("another error...");
        }

        if (response.HasNoErrors)
        {
            response.Whatever = ...
        }

        ...
        return response;
    }
}
class Controller
{
    private UseCase useCase;

    public Controller(UseCase useCase)
    {
        this.useCase = useCase;
    }

    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        Response response = useCase.Execute(request);
        Presenter presenter = new Presenter();
        if (response.ErrorMessages.Count > 0)
        {
            if (response.ErrorMessages.Contains(<invalid request>))
            {
                presenter.ShowError("...");
            }
            else if (response.ErrorMessages.Contains("another error")
            {
                presenter.ShowError("another error...");
            }
        }
        else
        {
            presenter.Show(response);
        }
    }
}

나는 구문 분석 좋아하지 않아 response내부 오류에 대한 데이터를 controller우리가 우리가 뭔가를 변경하는 경우 우리가 --- 중복 작업을하고 있다는 것을 할 경우 때문에 interactor에서, 우리는 또한 변경해야 뭔가 controller.

또한 나중에 interactor콘솔을 사용하여 데이터를 표시하는 데 재사용하기로 결정한 경우 콘솔 앱 if/else에있는 모든 데이터를 복사하여 붙여 controller넣어야합니다.

// in the controller for our console app
if (response.ErrorMessages.Count > 0)
{
    if (response.ErrorMessages.Contains(<invalid request>))
    {
        presenterForConsole.ShowError("...");
    }
    else if (response.ErrorMessages.Contains("another error")
    {
        presenterForConsole.ShowError("another error...");
    }
}
else
{
    presenterForConsole.Present(response);
}

우리는 옵션 # 1을 사용하는 경우 우리는이있을 것이다 if/else 단 하나 개의 장소에서 다음을 interactor.


ASP.NET MVC (또는 다른 유사한 MVC 프레임 워크)를 사용하는 경우 옵션 # 2가 더 쉬운 방법입니다.

그러나 우리는 여전히 이런 종류의 환경에서 옵션 1을 할 수 있습니다. 다음은 ASP.NET MVC에서 옵션 # 1을 수행하는 예입니다.

( public IActionResult ResultASP.NET MVC 앱 발표자 에게 있어야 함에 유의하십시오. )

class UseCase
{
    private Repository repository;

    public UseCase(Repository repository)
    {
        this.repository = repository;
    }

    public void Execute(Request request, Presenter presenter)
    {
        if (<invalid request>) 
        {
            this.presenter.ShowError("...");
            return;
        }

        if (<there is another error>) 
        {
            this.presenter.ShowError("another error...");
            return;
        }

        ...
        Response response = new Response() {
            ...
        }
        this.presenter.Show(response);
    }
}
// controller for ASP.NET app

class AspNetController
{
    private UseCase useCase;

    public AspNetController(UseCase useCase)
    {
        this.useCase = useCase;
    }

    [HttpPost("dosomething")]
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new AspNetPresenter();
        useCase.Execute(request, presenter);
        return presenter.Result;
    }
}
// presenter for ASP.NET app

public class AspNetPresenter
{
    public IActionResult Result { get; private set; }

    public AspNetPresenter(...)
    {
    }

    public async void Show(Response response)
    {
        Result = new OkObjectResult(new { });
    }

    public void ShowError(string errorMessage)
    {
        Result = new BadRequestObjectResult(errorMessage);
    }
}

( public IActionResult ResultASP.NET MVC 앱 발표자 에게 있어야 함에 유의하십시오. )

콘솔 용으로 다른 앱을 만들기로 결정한 경우 UseCase위 의 내용을 재사용 하고 콘솔 ControllerPresenter콘솔 만 만들 수 있습니다 .

// controller for console app

class ConsoleController
{    
    public void ExecuteUseCase(Data data)
    {
        Request request = new Request() 
        {
            Whatever = data.whatever,
        };
        var presenter = new ConsolePresenter();
        useCase.Execute(request, presenter);
    }
}
// presenter for console app

public class ConsolePresenter
{
    public ConsolePresenter(...)
    {
    }

    public async void Show(Response response)
    {
        // write response to console
    }

    public void ShowError(string errorMessage)
    {
        Console.WriteLine("Error: " + errorMessage);
    }
}

( public IActionResult Result우리는 콘솔 응용 프로그램의 발표자에 있지 않습니다 )


기여해 주셔서 감사합니다. 그러나 대화를 읽으면 이해할 수없는 한 가지가 있습니다. 그는 발표자가 응답에서 오는 데이터를 렌더링해야하며 동시에 상호 작용자가 응답을 작성해서는 안된다고 말합니다. 그러나 누가 응답을 만들고 있습니까? 어댑터 계층이 응용 프로그램 계층에 의존 할 수 있기 때문에 상호 작용자는 발표자에게 알려진 응용 프로그램 특정 형식으로 데이터를 발표자에게 제공해야한다고 말하고 싶습니다.
swahnee

죄송 해요. 토론의 코드 예제를 포함하지 않았기 때문에 혼란 스러울 수 있습니다. 코드 예제를 포함하도록 업데이트하겠습니다.
Jboy Flaga

밥 아저씨는 응답자가 상호 작용에 의해 생성되어서는 안된다고 말하지 않았습니다. 응답은 인터랙 터에 의해 작성됩니다 . 밥 아저씨가 말하고있는 것은 인터랙 터에 의해 생성 된 응답은 발표자에 의해 사용될 것입니다. 그런 다음 발표자는 "형식화"하고 형식화 된 응답을 뷰 모델에 넣은 다음 해당 뷰 모델을 뷰에 전달합니다. <br/> 이것이 내가 이해하는 방식입니다.
Jboy Flaga

1
더 이해가 되네요. Clean Architecture는 "view"도 "viewmodel"도 언급하지 않았기 때문에 "view"가 "presenter"와 동의어라는 인상을 받았습니다. 개 작자, 번안 자, 제작자.
swahnee

2

유스 케이스에는 발표자 또는 리턴 데이터가 포함될 수 있으며 애플리케이션 플로우에 필요한 사항에 따라 다릅니다.

다른 응용 프로그램 흐름을 이해하기 전에 몇 가지 용어를 이해하겠습니다.

  • 도메인 오브젝트 : 도메인 오브젝트는 비즈니스 로직 조작이 수행되는 도메인 계층의 데이터 컨테이너입니다.
  • 모델보기 : 도메인 개체는 일반적으로 응용 프로그램 계층에서 모델을보기 위해 매핑되어 사용자 인터페이스와 호환되고 친숙합니다.
  • 발표자 : 애플리케이션 계층의 컨트롤러는 일반적으로 유스 케이스를 호출하지만 도메인을 위임하여 모델 맵핑 로직을 별도의 클래스 (Single Responsibility Principle에 따름)로 표시하는 것이 좋습니다.

반환 데이터가 포함 된 사용 사례

일반적인 경우, 유스 케이스는 단순히 도메인 오브젝트를 애플리케이션 계층으로 리턴하여 애플리케이션 계층에서 추가로 처리하여 UI에 표시하기 쉽게 만들 수 있습니다.

컨트롤러가 유스 케이스를 호출 할 책임이 있기 때문에이 경우에는 렌더링 할 뷰로 보내기 전에 도메인에서 뷰로 모델 맵핑을 수행하기 위해 해당 프리젠터의 참조도 포함합니다.

다음은 간단한 코드 샘플입니다.

namespace SimpleCleanArchitecture
{
    public class OutputDTO
    {
        //fields
    }

    public class Presenter 
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    public class Domain
    {
        //fields
    }

    public class UseCaseInteractor
    {
        public Domain Process(Domain domain)
        {
            // additional processing takes place here
            return domain;
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            UseCaseInteractor userCase = new UseCaseInteractor();
            var domain = userCase.Process(new Domain());//passing dummy domain(for demonstration purpose) to process
            var presenter = new Presenter();//presenter might be initiated via dependency injection.

            return new View(presenter.Present(domain));
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

발표자를 포함하는 사용 사례

흔하지는 않지만 유스 케이스가 발표자를 호출해야 할 수도 있습니다. 이 경우 발표자의 구체적인 참조를 유지하는 대신 인터페이스 (또는 추상 클래스)를 참조 지점 (종속성 주입을 통해 런타임으로 초기화해야 함)으로 간주하는 것이 좋습니다.

도메인이 컨트롤러 내부가 아닌 별도의 클래스에서 모델 매핑 논리를 볼 수 있도록하면 컨트롤러와 사용 사례 간의 순환 종속성이 손상됩니다 (사용 사례 클래스에서 매핑 논리에 대한 참조가 필요한 경우).

여기에 이미지 설명을 입력하십시오

아래는 원본 기사에 설명 된대로 제어 흐름을 단순화하여 구현하는 방법을 보여줍니다. 단순화를 위해 다이어그램에 표시된 것과 달리 UseCaseInteractor 는 구체적 클래스입니다.

namespace CleanArchitectureWithPresenterInUseCase
{
    public class Domain
    {
        //fields
    }

    public class OutputDTO
    {
        //fields
    }

    // Use Case Output Port
    public interface IPresenter
    {
        OutputDTO Present(Domain domain);
    }

    public class Presenter: IPresenter
    {
        public OutputDTO Present(Domain domain)
        {
            // Mapping takes action. Dummy object returned for demonstration purpose
            // Usually frameworks like automapper to the mapping job.
            return new OutputDTO();
        }
    }

    // Use Case Input Port / Interactor   
    public class UseCaseInteractor
    {
        IPresenter _presenter;
        public UseCaseInteractor (IPresenter presenter)
        {
            _presenter = presenter;
        }

        public OutputDTO Process(Domain domain)
        {
            return _presenter.Present(domain);
        }
    }

    // A simple controller. 
    // Usually frameworks like asp.net mvc provides url routing mechanism to reach here through this type of class.
    public class Controller
    {
        public View Action()
        {
            IPresenter presenter = new Presenter();//presenter might be initiated via dependency injection.
            UseCaseInteractor userCase = new UseCaseInteractor(presenter);
            var outputDTO = userCase.Process(new Domain());//passing dummy domain (for demonstration purpose) to process
            return new View(outputDTO);
        }
    }

    // A simple view. 
    // Usually frameworks like asp.net mvc provides mechanism to render html based view through this type of class.
    public class View
    {
        OutputDTO _outputDTO;

        public View(OutputDTO outputDTO)
        {
            _outputDTO = outputDTO;
        }

    }
}

1

@CandiedOrange의 답변에 일반적으로 동의하지만 상호 작용자가 데이터를 다시 실행하여 컨트롤러가 발표자에게 전달하는 접근 방식에서도 이점을 볼 수 있습니다.

예를 들어 Asp.Net MVC의 맥락에서 Clean Architecture (종속성 규칙)의 아이디어를 사용하는 간단한 방법입니다.

이 토론에 대해 자세히 알아보기 위해 블로그 게시물을 작성했습니다. https://plainionist.github.io/Implementing-Clean-Architecture-Controller-Presenter/


1

발표자를 포함하거나 데이터를 반환하는 사용 사례?

그렇다면이 두 가지 대안 중 어느 것이 클린 아키텍처에 따른 유스 케이스 출력 포트의 "올바른"해석입니까? 둘 다 가능합니까?


한마디로

그렇습니다. 두 가지 접근 방식 모두 비즈니스 계층과 전달 메커니즘 간의 제어 역전 을 고려하는 한 실행 가능 합니다. 두 번째 접근법으로 우리는 관찰자를 사용하여 IOC를 도입 할 수 있으며 중재자는 다른 디자인 패턴을 거의 사용하지 않습니다 ...

자신과 함께 깨끗한 아키텍처 , 삼촌 밥의 시도는 합성 우리는 OOP의 원칙을 광범위하게 준수하는 중요한 개념 및 구성 요소를 공개하는 것으로 알려져 아키텍처의 무리입니다.

UML 클래스 다이어그램 (아래 다이어그램)을 고유 한 Clean Architecture 디자인 으로 간주하는 것이 역효과를 낳습니다 . 이 다이어그램은 구체적인 예제를 위해 그려 질 수 있었지만 … 일반적인 아키텍처 표현보다 훨씬 덜 추상적이기 때문에 구현 세부 사항 일 뿐인 인터랙 터 출력 포트 디자인 중에서 구체적으로 선택해야했습니다 .

Clean Architecture의 Uncle Bob UML 클래스 다이어그램


내 두 센트

나는 돌아 선호하는 주된 이유는 UseCaseResponse이 방법 내 사용 사례가 유지한다는 것이다 유연한 둘 수 있도록 구성 그들 사이 들이 generic ( 일반화특정 세대 ). 기본 예 :

// A generic "entity type agnostic" use case encapsulating the interaction logic itself.
class UpdateUseCase implements UpdateUseCaseInterface
{
    function __construct(EntityGatewayInterface $entityGateway, GetUseCaseInterface $getUseCase)
    {
        $this->entityGateway = $entityGateway;
        $this->getUseCase = $getUseCase;
    }

    public function execute(UpdateUseCaseRequestInterface $request) : UpdateUseCaseResponseInterface
    {
        $getUseCaseResponse = $this->getUseCase->execute($request);

        // Update the entity and build the response...

        return $response;
    }
}

// "entity type aware" use cases encapsulating the interaction logic WITH the specific entity type.
final class UpdatePostUseCase extends UpdateUseCase;
final class UpdateProductUseCase extends UpdateUseCase;

서로를 포함하거나 확장 하고 다른 주제 (엔터티)에서 재사용 가능한 것으로 정의 된 UML 사용 사례와 유사 합니다.


인터랙 터에서 데이터를 반환

그러나 유스 케이스는 실제 프리젠 테이션이 더 이상 수행되는 순간을 제어하지 않습니다 (예 : 로깅과 같이 해당 시점에 추가 작업을 수행하거나 필요한 경우 모두 중단하는 데 유용 할 수 있음).

이것이 무엇을 의미하는지 이해하지 못하는 경우, 왜 프리젠 테이션 수행을 "제어"해야합니까? 유스 케이스 응답을 리턴하지 않는 한 제어하지 않습니까?

유스 케이스는 응답 중에 상태 코드를 리턴하여 작업 중에 정확히 무슨 일이 있었는지 클라이언트 계층에 알릴 수 있습니다. HTTP 응답 상태 코드는 유스 케이스의 작동 상태를 설명하는 데 특히 적합합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.