DDD 애플리케이션 서비스와 REST API의 개념 불일치


20

복잡한 비즈니스 도메인과 REST API를 지원 해야하는 응용 프로그램 (REST가 아니라 리소스 지향)을 설계하려고합니다. 리소스 모델 방식으로 도메인 모델을 노출시키는 방법을 찾는 데 어려움이 있습니다.

DDD에서 도메인 모델의 클라이언트는 절차 적 '응용 프로그램 서비스'계층을 거쳐 엔티티 및 도메인 서비스로 구현 된 모든 비즈니스 기능에 액세스해야합니다. 예를 들어 User 엔터티를 업데이트하는 두 가지 방법이있는 응용 프로그램 서비스가 있습니다.

userService.ChangeName(name);
userService.ChangeEmail(email);

이 응용 프로그램 서비스의 API는 상태가 아닌 명령 (동사, 프로 시저)을 제공합니다.

그러나 동일한 애플리케이션에 RESTful API를 제공해야하는 경우 다음과 같은 사용자 자원 모델이 있습니다.

{
name:"name",
email:"email@mail.com"
}

자원 지향 API는 명령이 아닌 상태를 노출 합니다 . 이로 인해 다음과 같은 문제가 발생합니다.

  • REST API에 대한 각 업데이트 조작은 자원 모델에서 업데이트되는 특성에 따라 하나 이상의 Application Service 프로 시저 호출에 맵핑 될 수 있습니다.

  • 각 업데이트 작업은 REST API 클라이언트에 대한 원자적인 것처럼 보이지만 그렇게 구현되지는 않았습니다. 각 응용 프로그램 서비스 호출은 별도의 트랜잭션으로 설계되었습니다. 자원 모델에서 하나의 필드를 업데이트하면 다른 필드의 유효성 검사 규칙이 변경 될 수 있습니다. 따라서 모든 자원 모델 필드를 함께 유효성 검증하여 모든 잠재적 응용 프로그램 서비스 호출이 유효한지 확인해야합니다. 한 번에 일련의 명령을 검증하는 것은 한 번에 하나씩 수행하는 것보다 훨씬 덜 간단합니다. 개별 명령조차도 모르는 클라이언트에서 어떻게합니까?

  • 다른 순서로 Application Service 메소드를 호출하면 다른 영향을 줄 수 있지만 REST API는 차이가없는 것처럼 보입니다 (하나의 자원 내).

더 비슷한 문제가 발생할 수 있지만 기본적으로 모두 같은 문제로 인해 발생합니다. 응용 프로그램 서비스를 호출 할 때마다 시스템 상태가 변경됩니다. 유효한 변경의 규칙, 엔터티가 다음 변경을 수행 할 수있는 작업 집합입니다. 자원 지향 API는 모든 것을 원자 연산처럼 보이게합니다. 그러나이 격차를 극복하는 복잡성은 어딘가에 가야만한다.

또한 UI가 명령 지향적 인 경우가 많으며 종종 클라이언트 측의 명령과 리소스를 매핑 한 다음 API 측으로 다시 매핑해야합니다.

질문 :

  1. 이 모든 복잡성을 (두껍고) REST-to-AppService 맵핑 계층으로 처리해야합니까?
  2. 아니면 DDD / REST에 대한 이해가 부족합니까?
  3. REST는 단순히 (매우 낮은) 복잡성에 걸쳐 도메인 모델의 기능을 노출시키는 데 실용적이지 않을 수 있습니까?

3
개인적으로 필요한 REST를 고려하지 않습니다. 그러나 DDD를 넣을 수 있습니다. infoq.com/articles/rest-api-on-cqrs programmers.stackexchange.com/questions/242884/… blog.42.nl/articles/rest-and-ddd-incompatible
Den

REST 클라이언트를 시스템의 사용자로 생각하십시오. 시스템이 수행하는 작업을 수행하는 방법에 대해서는 전혀 신경 쓰지 않습니다. 더 이상 REST 클라이언트가 사용자가 기대하는 것보다 도메인에서 다른 모든 조치를 알 것으로 기대하지 않습니다. 이 논리는 어느 곳으로 가야하지만 어느 시스템에서나 가야 만합니다. REST를 사용하지 않는다면 클라이언트로 옮기는 것입니다. 이를 수행하지 않는 것은 REST의 요점이므로 클라이언트는 상태를 업데이트하려는 것만 알고 있어야하며 어떻게 진행 해야하는지 모릅니다.
Cormac Mulhall

2
@astr 간단한 대답은 리소스가 모델이 아니므로 리소스 처리 코드 디자인이 모델 디자인에 영향을 미치지 않아야한다는 것입니다. 리소스는 모델이 내부에있는 시스템의 외부 측면입니다. UI를 생각할 때와 같은 방식으로 리소스를 생각하십시오. 사용자가 UI에서 단일 버튼을 클릭하면 모델에서 수백 가지의 일이 발생합니다. 자원과 유사합니다. 클라이언트는 리소스 (단일 PUT 문)를 업데이트하며 모델에서 백만 가지 다른 일이 발생할 수 있습니다. 모델을 리소스에 밀접하게 연결하는 것은 안티 패턴입니다.
Cormac Mulhall

1
이는 도메인의 조치를 REST 상태 변경의 부작용으로 처리하고 도메인과 웹을 분리하여 (수분이 많은 비트의 경우 25 분으로 빠르게) yow.eventer.com/events/1004/talks/1047
Cormac Mulhall

1
나는 또한 "로봇 / 상태 머신으로서의 사용자"전체에 대해 확신하지 못한다. 우리는 사용자 인터페이스를 그보다 훨씬 자연스럽게 만들기 위해 노력해야한다고 생각합니다.
guillaume31

답변:


10

REST 리소스를 다르게 모델링하여 같은 문제를 겪고이를 해결했습니다. 예 :

/users/1  (contains basic user attributes) 
/users/1/email 
/users/1/activation 
/users/1/address

기본적으로 더 크고 복잡한 리소스를 여러 개의 작은 리소스로 나눕니다. 이들 각각은 함께 처리 될 것으로 예상되는 원래 자원의 다소 응집 된 속성 그룹을 포함합니다.

이러한 리소스에 대한 각 작업은 여러 서비스 메소드를 사용하여 구현 될 수 있지만 원 자성입니다. 적어도 Spring / Java EE에서는 원래 자체 트랜잭션을 갖도록 의도 된 여러 메소드에서 더 큰 트랜잭션을 작성하는 데 문제가되지 않습니다 (필수 트랜잭션 사용). 번식). 이 특수 자원에 대해 추가 검증을 수행해야하는 경우가 종종 있지만 속성이 응집력이 있기 때문에 여전히 관리가 용이합니다.

보다 세분화 된 리소스는 리소스로 쉽게 표현할 수 없기 때문에 클라이언트와 서버 모두에서이 논리를 사용하는 대신 더 세밀한 리소스를 사용하여 수행 할 수있는 작업에 대한 자세한 정보를 전달하기 때문에 HATEOAS 방식에도 좋습니다.

UI가 이러한 리소스 (특히 데이터 지향 UI)를 염두에두고 모델링되지 않은 경우 몇 가지 문제가 발생할 수 있습니다. 예를 들어 UI는 지정된 리소스 (및 해당 하위 리소스)의 모든 속성에 큰 형태를 제공하며 모두 편집하고 한 번에 저장-클라이언트가 여러 가지 자원 작업 (자체는 원자 적이지만 전체 시퀀스는 원자 적이 지 않음)을 호출해야하지만 원자 성의 환상을 만듭니다.

또한 이러한 자원의 분할은 때때로 쉽지 않거나 명백하지 않습니다. 복잡한 동작 / 라이프 사이클이있는 리소스를 중심으로 복잡성을 관리합니다.


이것이 제가 생각한 것입니다. 쓰기 작업에 더 편리하기 때문에보다 세분화 된 리소스 표현을 만듭니다. 리소스가 세분화 될 때 리소스 쿼리를 어떻게 처리합니까? 읽기 전용의 비정규 화 된 표현도 만드시겠습니까?
astreltsov

1
아니요, 읽기 전용의 비정규 화 된 표현이 없습니다. jsonapi.org 표준을 사용 하며 주어진 자원에 대한 응답에 관련 자원을 포함시키는 메커니즘이 있습니다. 기본적으로 나는 "ID가 1 인 사용자에게 이메일을 제공하고 활성화합니다"라고 말합니다. 이는 서브 리소스에 대한 추가 REST 호출을 제거하는 데 도움이되며 훌륭한 JSON API 클라이언트 라이브러리를 사용하는 경우 서브 리소스를 처리하는 클라이언트의 복잡성에는 영향을 미치지 않습니다.
qbd

따라서 서버의 단일 GET 요청은 하나 이상의 실제 쿼리로 변환되며 (포함 된 하위 리소스 수에 따라) 단일 리소스 개체로 결합됩니까?
astreltsov

둘 이상의 중첩 수준이 필요한 경우 어떻게합니까?
astreltsov

예, 관계형 DB에서는 아마도 여러 쿼리로 변환 될 것입니다. 임의 중첩은 JSON API에서 지원되며 여기에 설명되어 있습니다. jsonapi.org/format/#fetching-includes
qbd

0

여기서 중요한 문제는 REST 호출이 이루어질 때 비즈니스 로직이 어떻게 투명하게 호출됩니까? 이것은 REST에 의해 직접 해결되지 않는 문제입니다.

JPA와 같은 지속성 공급자를 통해 자체 데이터 관리 계층을 만들어이 문제를 해결했습니다. 사용자 정의 어노테이션이있는 메타 모델을 사용하여 엔티티 상태가 변경 될 때 적절한 비즈니스 로직을 호출 할 수 있습니다. 이를 통해 엔티티 상태가 비즈니스 로직을 변경하는 방식에 관계없이 비즈니스 로직이 호출됩니다. 아키텍처 DRY와 비즈니스 로직을 한 곳에 유지합니다.

위 예제를 사용하여 이름 필드가 REST를 사용하여 변경 될 때 validateName이라는 비즈니스 로직 메소드를 호출 할 수 있습니다.

class User { 
      String name;
      String email;

      /**
       * This method will be transparently invoked when the value of name is changed
       * by REST.
       * The XorUpdate annotation becomes effective for PUT/POST actions
       */
      @XorPostChange
      public void validateName() {
        if(name == null) {
          throw new IllegalStateException("Name cannot be set as null");
        }
      }
    }

그러한 도구를 마음대로 사용하면 비즈니스 논리 방법에 적절하게 주석을 달기 만하면됩니다.


0

리소스 모델 방식으로 도메인 모델을 노출시키는 방법을 찾는 데 어려움이 있습니다.

리소스 모델 방식으로 도메인 모델을 노출해서는 안됩니다. 자원 지향 방식으로 응용 프로그램을 노출해야합니다.

UI가 명령 지향적 인 경우가 많으며 종종 클라이언트 측의 명령과 자원을 매핑 한 다음 API 측으로 다시 매핑해야합니다.

전혀 아님-도메인 모델과 인터페이스하는 응용 프로그램 자원으로 명령을 보냅니다.

REST API에 대한 각 업데이트 조작은 자원 모델에서 업데이트되는 특성에 따라 하나 이상의 Application Service 프로 시저 호출에 맵핑 될 수 있습니다.

그렇습니다. 약간 더 간단한 방법으로이 방법을 간단하게 만들 수 있습니다. REST API에 대한 각 업데이트 작업은 하나 이상의 집계에 명령을 디스패치하는 프로세스에 매핑됩니다.

각 업데이트 작업은 REST API 클라이언트에 대한 원자적인 것처럼 보이지만 그렇게 구현되지는 않았습니다. 각 응용 프로그램 서비스 호출은 별도의 트랜잭션으로 설계되었습니다. 자원 모델에서 하나의 필드를 업데이트하면 다른 필드의 유효성 검사 규칙이 변경 될 수 있습니다. 따라서 모든 자원 모델 필드를 함께 유효성 검증하여 모든 잠재적 응용 프로그램 서비스 호출이 유효한지 확인해야합니다. 한 번에 일련의 명령을 검증하는 것은 한 번에 하나씩 수행하는 것보다 훨씬 덜 간단합니다. 개별 명령조차도 모르는 클라이언트에서 어떻게합니까?

당신은 여기서 잘못된 꼬리를 쫓고 있습니다.

상상해보십시오 : 그림에서 REST를 완전히 제거하십시오. 대신이 응용 프로그램의 데스크탑 인터페이스를 작성한다고 가정하십시오. 디자인 요구 사항이 정말 우수하고 작업 기반 UI를 구현하고 있다고 상상해보십시오. 따라서 사용자는 작업중인 작업에 맞게 완벽하게 조정 된 미니멀리즘 인터페이스를 얻게됩니다. 사용자는 일부 입력을 지정한 다음 "VERB!" 단추.

지금 벌어지는 일은? 사용자 관점에서 볼 때 이것은 단일 원자 작업입니다. domainModel의 관점에서 볼 때 각 명령이 별도의 트랜잭션으로 실행되는 집계에 의해 실행되는 많은 명령입니다. 그것들은 완전히 호환되지 않습니다! 격차를 해소하기 위해 중간에 무언가가 필요합니다!

뭔가 "응용 프로그램"입니다.

올바른 경로에서 응용 프로그램은 일부 DTO를 수신하고 해당 개체를 구문 분석하여 이해하는 메시지를 얻고 메시지의 데이터를 사용하여 하나 이상의 집계에 대해 올바른 형식의 명령을 만듭니다. 응용 프로그램은 집계에 전달하는 각 명령이 제대로 구성되어 있는지 (작업중인 부패 방지 계층) 확인하고 집계를로드하고 트랜잭션이 성공적으로 완료되면 집계를 저장합니다. 집계는 현재 상태에 따라 명령이 유효한지 자체적으로 결정합니다.

가능한 결과-명령이 모두 성공적으로 실행 됨-부패 방지 계층이 메시지를 거부합니다. 일부 명령이 성공적으로 실행되었지만 집계 중 하나가 불만을 표시하면 완화 할 수 있습니다.

자, 당신은 그 응용 프로그램을 구축했다고 상상해보십시오; RESTful 방식으로 어떻게 상호 작용합니까?

  1. 클라이언트는 하이퍼 미디어 컨트롤을 포함하여 현재 상태에 대한 하이퍼 미디어 설명 (예 : 작업 기반 UI)으로 시작합니다.
  2. 클라이언트는 작업 (즉, DTO)의 표현을 리소스로 발송합니다.
  3. 리소스는 들어오는 HTTP 요청을 구문 분석하고 표현을 잡고 응용 프로그램에 전달합니다.
  4. 응용 프로그램이 작업을 실행합니다. 자원의 관점에서 볼 때 이것은 다음 결과 중 하나를 갖는 블랙 박스입니다.
    • 응용 프로그램이 모든 집계를 성공적으로 업데이트했습니다. 자원이 클라이언트에 성공을보고하여 새 응용 프로그램 상태로 보냅니다.
    • 부패 방지 계층은 메시지를 거부합니다. 자원은 클라이언트에 4xx 오류 (아마도 잘못된 요청)를보고하여 발생한 문제에 대한 설명을 전달합니다.
    • 응용 프로그램은 일부 집계를 업데이트합니다. 리소스는 클라이언트에게 명령이 수락되었음을보고하고 클라이언트에게 명령의 진행률을 나타내는 리소스를 보냅니다.

응용 프로그램이 클라이언트에 응답 한 후 비동기 명령을 수락 할 때 일반적으로 사용되는 메시지 처리를 연기 할 때 일반적으로 허용되는 것이 허용됩니다. 그러나 원자 적 인 작업이 완화되어야하는이 경우에도 잘 작동합니다.

이 관용구에서 리소스는 작업 자체를 나타냅니다. 작업 리소스에 적절한 표현을 게시하여 작업의 새 인스턴스를 시작하고 해당 리소스가 응용 프로그램과 인터페이스하여 다음 응용 프로그램 상태로 안내합니다.

에서 여러 명령을 조정할 때마다 프로세스 (일명 비즈니스 프로세스, 일명 saga) 측면에서 생각하고 싶습니다.

읽기 모델에도 비슷한 개념의 불일치가 있습니다. 다시 한 번, 작업 기반 인터페이스를 고려하십시오. 작업에 여러 집계를 수정해야하는 경우 작업 준비를위한 UI에 여러 집계의 데이터가 포함되어있을 수 있습니다. 리소스 구성표가 집계와 1 : 1이면 정렬하기가 어렵습니다. 대신, 위에서 설명한대로 "시작 작업"관계를 작업 끝점에 매핑하는 하이퍼 미디어 컨트롤과 함께 여러 집계에서 데이터 표현을 반환하는 리소스를 제공하십시오.

Jim Webber의 REST in Practice 도 참조하십시오 .


사용 사례에 따라 도메인과 상호 작용하도록 API를 설계하는 경우 Sagas가 전혀 필요하지 않은 방식으로 설계하지 않는 이유는 무엇입니까? 어쩌면 나는 뭔가 빠졌지 만 귀하의 응답을 읽음으로써 REST는 DDD와 잘 어울리지 않으며 원격 절차 (RPC)를 사용하는 것이 좋습니다. DDD는 동작 중심이며 REST는 http 동사 중심입니다. 그림에서 REST를 제거하고 API에서 동작 (명령)을 노출하지 않는 이유는 무엇입니까? 결국, 이들은 유스 케이스 시나리오를 충족하도록 설계되었으며 문제는 트랜잭션입니다. UI를 소유 한 경우 REST의 장점은 무엇입니까?
iberodev
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.