"비즈니스 로직은 모델이 아닌 서비스에 있어야 얼마나 정확합니까?"


397

상태

오늘 저녁 나는 준 대답 에 StackOverflow에 대한 질문을.

질문:

기존 객체의 편집은 저장소 계층 또는 서비스에서 수행해야합니까?

예를 들어 부채가있는 사용자가있는 경우. 나는 그의 빚을 바꾸고 싶다. 객체를 가져 와서 편집하고 저장하여 BuyingService와 같은 UserRepository 또는 서비스에서해야합니까?

내 대답 :

객체를 동일한 객체로 변경하는 책임은 그대로두고 리포지토리를 사용하여이 객체를 검색해야합니다.

상황 예 :

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

내가받은 의견 :

비즈니스 로직은 실제로 서비스에 있어야합니다. 모델에 없습니다.

인터넷은 무엇을 말합니까?

따라서 서비스 레이어를 실제로 (의식적으로) 사용한 적이 없기 때문에 검색이 가능합니다. 서비스 계층 패턴과 작업 단위 패턴을 읽기 시작했지만 지금까지 서비스 계층을 사용해야한다고 확신 할 수 없습니다.

Anemic Domain Model의 안티 패턴에 대한 Martin Fowler 의이 기사 를 예로 들어 보겠습니다 .

도메인 공간에는 명사의 이름을 따서 명명 된 객체가 많이 있으며, 이러한 객체는 실제 도메인 모델의 풍부한 관계 및 구조와 연결됩니다. 캐치는 당신이 행동을 볼 때오고, 당신은 이러한 객체에 행동이 거의 없다는 것을 깨닫고 게터와 세터의 가방 이상을 만들지 않습니다. 실제로 이러한 모델에는 도메인 논리를 도메인 개체에 넣지 말아야한다는 디자인 규칙이 있습니다. 대신 모든 도메인 논리를 캡처하는 일련의 서비스 개체가 있습니다. 이러한 서비스는 도메인 모델 위에 있으며 도메인 모델을 데이터에 사용합니다.

(...) 도메인 개체에 있어야하는 논리는 유효성 검사, 계산, 비즈니스 규칙 등 도메인 논리입니다.

나에게 이것은 정확히 상황에 관한 것 같았습니다. 나는 그 클래스 안에 메소드를 도입하여 객체의 데이터 조작을 옹호했습니다. 그러나 이것은 어느 쪽이든 주어져야한다는 것을 알고 있으며 아마도 리포지토리를 사용하여 이러한 메소드를 호출하는 방법과 더 관련이 있습니다.

또한이 기사 (아래 참조)에서 서비스 계층은 실제 작업 집약적 계층보다 기본 모델에 작업을 위임 하는 외관으로 간주된다는 느낌이 들었습니다 .

응용 프로그램 계층 [서비스 계층의 이름] : 소프트웨어가 수행해야하는 작업을 정의하고 표현 도메인 개체가 문제를 해결하도록 지시합니다. 이 계층이 담당하는 작업은 비즈니스에 의미가 있거나 다른 시스템의 응용 프로그램 계층과 상호 작용하는 데 필요합니다. 이 층은 얇게 유지됩니다. 여기에는 비즈니스 규칙이나 지식이 포함되지 않지만 작업을 조정하고 다음 계층에서 도메인 개체의 공동 작업에 대한 작업 만 위임합니다. 비즈니스 상황을 나타내는 상태는 없지만 사용자 또는 프로그램의 작업 진행률을 나타내는 상태는 가질 수 있습니다.

어느 것이 여기 에 강화 됩니다 :

서비스 인터페이스. 서비스는 모든 인바운드 메시지가 전송되는 서비스 인터페이스를 제공합니다. 서비스 인터페이스는 애플리케이션에서 구현 된 비즈니스 로직 (일반적으로 비즈니스 계층의 로직)을 잠재적 인 소비자에게 노출시키는 외관으로 생각할 수 있습니다.

그리고 여기 :

서비스 계층에는 응용 프로그램이나 비즈니스 논리가 없어야하며 주로 몇 가지 문제에 중점을 두어야합니다. Business Layer 통화를 래핑하고 고객이 이해할 수있는 공용 언어로 도메인을 번역하며 서버와 요청하는 클라이언트 간의 통신 매체를 처리해야합니다.

이것은 서비스 계층에 대해 이야기 하는 다른 리소스 와는 심각하게 대조됩니다 .

서비스 계층은 동일한 트랜잭션에 속하는 조치의 작업 단위 인 메소드가있는 클래스로 구성되어야합니다.

또는 내가 이미 연결 한 질문에 대한 두 번째 답변 :

어느 시점에서 응용 프로그램은 비즈니스 로직을 원할 것입니다. 또한 요청이 악의적이거나 성능이 저하되지 않도록 입력의 유효성을 검사 할 수 있습니다. 이 논리는 서비스 계층에 속합니다.

"해결책"?

이 답변 의 지침에 따라 서비스 계층을 사용하는 다음과 같은 접근법을 생각해 냈습니다.

class UserController : Controller {
    private UserService _userService;

    public UserController(UserService userService){
        _userService = userService;
    } 

    public ActionResult MakeHimPay(string username, int amount) {
        _userService.MakeHimPay(username, amount);
        return RedirectToAction("ShowUserOverview");
    }

    public ActionResult ShowUserOverview() {
        return View();
    }
}

class UserService {
    private IUserRepository _userRepository;

    public UserService(IUserRepository userRepository) {
        _userRepository = userRepository;
    }

    public void MakeHimPay(username, amount) {
        _userRepository.GetUserByName(username).makePayment(amount);
    }
}

class UserRepository {
    public User GetUserByName(string name){
        // Get appropriate user from database
    }
}

class User {
    private int debt; // debt in cents
    private string name;

    // getters

    public void makePayment(int cents){
        debt -= cents;
    }
}

결론

컨트롤러의 코드가 서비스 계층으로 옮겨졌습니다. 그러나 이것은 원래의 대답과 관련이없는 것처럼 보입니다.

나는 디자인 패턴이 지침이 아니라 가능할 때마다 구현되어야 할 규칙이 아니라는 것을 알고 있습니다. 그러나 나는 서비스 계층에 대한 결정적인 설명과 그것을 어떻게 고려해야하는지 찾지 못했다.

  • 단순히 컨트롤러에서 로직을 추출하여 대신 서비스에 넣는 수단입니까?

  • 컨트롤러와 도메인간에 계약을 맺어야합니까?

  • 도메인과 서비스 계층 사이에 계층이 있어야합니까?

그리고 마지막으로 : 원래 의견에 따름

비즈니스 로직은 실제로 서비스에 있어야합니다. 모델에 없습니다.

  • 이 올바른지?

    • 모델 대신 서비스에서 비즈니스 로직을 어떻게 소개합니까?

6
서비스 계층을 피할 수없는 트랜잭션 스크립트 부분을 집계 루트에 배치하는 장소로 취급합니다. 서비스 계층이 너무 복잡 해져서 Anemic Model 방향으로 이동하고 도메인 모델에주의를 기울이고 검토해야한다는 신호가 표시됩니다. 또한 그 특성에 따라 복제되지 않는 논리를 SL에 넣으려고합니다.
Pavel Voronin

서비스는 모델 계층의 일부라고 생각했습니다. 나는 그것을 잘못 생각하고 있습니까?
Florian Margaine

나는 경험의 법칙을 사용합니다 : 당신이 필요로하지 않는 것에 의존하지 마십시오; 그렇지 않으면 필요하지 않은 부분의 변경으로 인해 (보통 부정적인) 영향을받을 수 있습니다. 결과적으로, 나는 명확하게 정의 된 많은 역할로 끝납니다. 내 데이터 객체에는 모든 클라이언트가 사용하는 한 동작이 포함됩니다. 그렇지 않으면 동작을 필요한 역할을 구현하는 클래스로 옮깁니다.
beluchin 2016 년

1
어플리케이션 흐름 제어 로직은 컨트롤러에 속합니다. 데이터 액세스 논리는 저장소에 속합니다. 유효성 검사 논리는 서비스 계층에 속합니다. 서비스 계층은 ASP.NET MVC 응용 프로그램에서 컨트롤러와 리포지토리 계층 간의 통신을 중재하는 추가 계층입니다. 서비스 계층에는 비즈니스 유효성 검사 논리가 포함되어 있습니다. 저장소. asp.net/mvc/view/older-versions-1/models-data/…
Kbdavis07

3
IMHO, 올바른 OOP 스타일 도메인 모델은 실제로 "비즈니스 서비스"를 피하고 비즈니스 로직을 모델에 유지해야합니다. 그러나 실제로는 명확하게 명명 된 메소드를 사용하여 거대한 비즈니스 계층을 작성하고 전체 메소드를 중심으로 트랜잭션 (또는 작업 단위)을 배치하려는 유혹을 느낍니다. 하나의 비즈니스 서비스 방법으로 여러 모델 클래스를 처리하는 것이 해당 모델 클래스 간의 관계를 계획하는 것보다 훨씬 쉽습니다. 질문에 답하기 어려운 질문이 있기 때문입니다. 집합 루트는 어디에 있습니까? 데이터베이스 트랜잭션을 어디서 시작 / 커밋해야합니까? 기타
JustAMartin

답변:


368

뭐라고 정의하기 위해 서비스의 책임은 먼저 무엇을 정의 할 필요가 서비스 입니다.

서비스 는 정식 또는 일반 소프트웨어 용어가 아닙니다. 실제로 Service클래스 이름 의 접미사 는 많이 정렬 된 Manager 와 매우 비슷합니다 . 객체가 실제로 수행 하는 작업에 대해서는 거의 알려주지 않습니다 .

실제로 서비스가해야 할 일은 아키텍처에 따라 다릅니다.

  1. 전통적인 계층 구조에서 서비스 는 문자 그대로 비즈니스 로직 계층 과 동의어입니다 . UI와 데이터 사이의 계층입니다. 따라서 모든 비즈니스 규칙이 서비스에 적용됩니다. 데이터 계층은 기본 CRUD 조작 만 이해해야하며 UI 계층은 프리젠 테이션 DTO와 비즈니스 오브젝트 간의 맵핑 만 처리해야합니다.

  2. RPC 스타일 분산 아키텍처 (SOAP, UDDI, BPEL 등)에서 서비스 는 물리적 엔드 포인트 의 논리적 버전입니다 . 기본적으로 관리자가 공개 API로 제공하려는 작업 모음입니다. 다양한 모범 사례 가이드는 서비스 운영 이 실제로는 CRUD가 아닌 비즈니스 수준의 운영이어야한다고 설명 하며 동의하는 경향이 있습니다.

    그러나 실제 원격 서비스를 통해 모든 것을 라우팅 하면 성능이 심각하게 저하 될 수 있으므로 일반적으로 이러한 서비스가 실제로 비즈니스 로직을 구현 하지 않는 것이 가장 좋습니다 . 대신 "내부"비즈니스 오브젝트 세트를 랩핑해야합니다. 단일 서비스에는 하나 이상의 비즈니스 오브젝트가 포함될 수 있습니다.

  3. MVP / MVC / MVVM / MV * 아키텍처에는 서비스 가 전혀 존재하지 않습니다. 또는 그렇게하는 경우이 용어는 컨트롤러 또는 뷰 모델에 주입 할 수있는 일반 객체를 지칭하는 데 사용됩니다. 비즈니스 로직은 모델에 있습니다. 복잡한 작업을 조율하기 위해 "서비스 개체"를 만들려면 구현 세부 정보로 볼 수 있습니다. 슬프게도 많은 사람들이 MVC를 이와 같이 구현하지만 모델 자체는 아무것도하지 않기 때문에 안티 패턴 ( Anemic Domain Model )으로 간주됩니다 .UI의 속성 일뿐입니다.

    어떤 사람들은 실수로 100 라인 컨트롤러 방식을 취하고이를 서비스에 적용하면 더 나은 아키텍처를 만들 수 있다고 생각합니다. 실제로는 그렇지 않습니다. 필요한 것은 다른 간접적 인 간접 계층을 추가하는 것입니다. 실제로 컨트롤러는 여전히 작업을 수행하고 있습니다. 이름이 잘못 지정된 "도우미"개체를 통해서만 수행됩니다. 빈혈 도메인 모델을 유용한 모델로 바꾸는 방법에 대한 명확한 예를 보려면 Jimmy Bogard의 Wicked Domain Models 프레젠테이션을 적극 권장 합니다. 노출하려는 모델과 비즈니스 상황 에서 실제로 어떤 작업이 유효한지 신중하게 검사해야합니다 .

    데이터베이스가 주문이 들어, 당신은 총 금액에 대한 열이있는 경우 예를 들어, 응용 프로그램은 아마 (A)은 역사의와 (b)는이 있어야 있기 때문에 실제로 임의의 값에 해당 필드를 변경할 수해서는 안 무엇에 의해 결정 순서뿐만 아니라 아마 다른 시간에 민감한 데이터 / 규칙. 사용자 코드가 여전히 실제 주문 오브젝트를 가져 와서 그 금액을 변경할 수 있기 때문에 주문 관리 서비스를 작성해도이 문제점을 해결할 필요는 없습니다 . 대신 주문 자체 는 안전하고 일관된 방식으로 만 변경할 수 있도록해야합니다.

  4. DDD에서 서비스는 특별히 집계 루트에 속하지 않는 작업이있는 상황을위한 것 입니다. 종종 서비스가 필요할 때 올바른 루트를 사용하지 않았 음을 암시 할 수 있으므로 여기에서주의해야합니다. 그러나 귀하가 한 것으로 가정하면 서비스는 여러 루트에서 작업을 조정하거나 때로는 도메인 모델과 관련이없는 문제 (예 : BI / OLAP 데이터베이스에 정보 쓰기)를 처리하는 데 사용됩니다.

    DDD 서비스의 주목할만한 특징 중 하나는 DDD 서비스가 트랜잭션 스크립트 를 사용할 수 있다는 것입니다 . 대규모 응용 프로그램에서 작업 할 때는 도메인 모델을 사용하는 것보다 T-SQL 또는 PL / SQL 프로 시저를 사용하여 작업을 수행하는 것이 훨씬 쉬운 인스턴스가 발생할 가능성이 큽니다. 이것은 정상이며 서비스에 속합니다.

    이는 서비스의 계층 구조 정의에서 근본적으로 벗어난 것입니다. 서비스 계층은 도메인 객체를 캡슐화합니다. DDD 서비스 는 도메인 객체에 없는 것을 캡슐화 하고 의미가 없습니다.

  5. 서비스 지향 아키텍처에서 서비스 는 비즈니스 기능에 대한 기술 권한으로 간주됩니다. 즉 , 비즈니스 데이터의 특정 하위 집합의 독점 소유자이며 해당 데이터를 읽을 수는 없습니다.

    필연적으로 서비스는 실제로 SOA의 엔드 투 엔드 제안입니다. 즉, 서비스는 전체 스택 과 같은 특정 구성 요소 가 아니며 전체 응용 프로그램 (또는 전체 비즈니스)은 메시징 및 UI 계층을 제외하고 교차하지 않고 나란히 실행되는 일련의 서비스입니다. 각 서비스에는 자체 데이터, 자체 비즈니스 규칙 및 자체 UI가 있습니다. 이들은 비즈니스 중심적이어야하기 때문에 서로 오케스트레이션 할 필요가 없으며, 비즈니스 자체와 마찬가지로 각 서비스에는 고유 한 책임이 있으며 다른 서비스와 다소 독립적으로 운영됩니다.

    그래서, SOA의 정의, 비즈니스 로직의 모든 부분은 어디 서비스 내에 포함되어 있지만, 다시, 그래서 전체입니다 시스템 . SOA의 서비스는 컴포넌트 를 가질 수 있고 엔드 포인트 를 가질 수 있지만 원래의 "S"가 의미하는 것과 충돌하기 때문에 코드를 서비스 로 호출하는 것은 상당히 위험합니다 .

    SOA는 일반적으로 메시징에 관심이 많기 때문에 이전에 서비스 에서 패키지화했을 수있는 작업 은 일반적으로 handlers에 캡슐화 되지만 다중성은 다릅니다. 각 핸들러는 하나의 메시지 유형, 하나의 작업을 처리합니다. 단일 책임 원칙에 대한 엄격한 해석 이지만 가능한 모든 작업이 자체 클래스에 있기 때문에 유지 관리가 용이합니다. 따라서 명령은 기술적 인 것이 아니라 비즈니스 운영을 나타 내기 때문에 중앙 집중식 비즈니스 로직 이 필요 하지 않습니다 .

궁극적으로, 어떤 아키텍처를 선택하든 대부분의 비즈니스 로직을 가진 구성 요소 나 계층이있을 것입니다. 결국, 비즈니스 로직이 곳곳에 흩어져 있다면 스파게티 코드 만 있으면됩니다. 그러나 해당 구성 요소를 서비스 라고 부르는지 여부 와 작업 수 또는 크기와 같은 측면에서 설계되는 방식은 아키텍처 목표에 달려 있습니다.

정답이나 오답은 없으며 상황에 맞는 것만 있습니다.


12
매우 정교하게 답변 해 주셔서 감사합니다. 내가 생각할 수있는 모든 것을 분명히했습니다. 다른 답변도 우수한 품질에 적합하지만이 답변이 모두 최고라고 생각 하므로이 답변을 수락합니다. 나는 다른 답변을 위해 여기에 추가 할 것입니다 : 절묘한 품질과 정보, 그러나 슬프게도 나는 당신에게 공감대를 줄 수 있습니다.
Jeroen Vannevel

2
전통적인 계층 구조에서 서비스는 비즈니스 논리 계층과 동의어라는 사실에 동의하지 않습니다.
CodeART

1
@CodeART : 3 계층 구조입니다. 나는 또한 "서비스"레이어라고도합니다,하지만 솔직히 유일한 장소는 내가이 구현 본 적이 프레젠테이션과 비즈니스 계층 (layer) 사이의 "응용 프로그램 계층"이있는 곳 4 계층 구조를 볼 성공적으로 거대한 불규칙하게 넓어있다가 SAP 또는 Oracle과 같은 무한 구성 가능한 비즈니스 용 제품은 여기에서 언급 할 가치가 있다고 생각하지 않았습니다. 원하는 경우 설명을 추가 할 수 있습니다.
Aaronaught

1
그러나 우리가 100 + 라인 컨트롤러 (예 : 컨트롤러가 메시지를 수락 한 다음 JSON 객체를 직렬화 해제하고 비즈니스 규칙을 적용하고 db에 저장하고 결과 객체를 반환 함)를 가져 와서 일부 로직을 service method does 우리가 따로 따로 고통없이 각 부분을 테스트 할 수 있도록 도와 주는가?
artjom

2
@Aaronaught ORM을 통해 도메인 객체를 db에 매핑하고 비즈니스 로직이없는 경우이 빈혈증 도메인 모델이 아닌지 한 가지만 설명하고 싶습니까?
artjom

40

당신의 제목에 관해서는 , 나는 그 질문이 의미가 있다고 생각하지 않습니다. MVC 모델은 데이터와 비즈니스 로직으로 구성됩니다. 모델이 아니라 서비스에 로직이 있어야한다고 말하는 것은 "승객은 차가 아닌 좌석에 앉아 있어야합니다"와 같습니다.

그런 다음 "모델"이라는 용어는 오버로드 된 용어입니다. 아마도 MVC 모델을 의미하지는 않았지만 DTO (Data Transfer Object) 의미의 모델을 의미했을 것입니다. 일명 엔터티. 이것이 Martin Fowler가 말하고있는 것입니다.

내가 보는 방식으로 Martin Fowler는 이상적인 세상에서 사물에 대해 이야기하고 있습니다. Hibernate와 JPA (Java 땅)의 실제 세계에서 DTO는 매우 누출 된 추상화입니다. 나는 나의 사업 논리를 제 존재에두고 싶습니다. 더 깔끔하게 만들 수 있습니다. 문제는 이러한 엔티티가 관리 / 캐시 된 상태로 존재할 수 있으며 이해하기 매우 어렵고 지속적으로 노력을 방해한다는 것입니다. 내 의견을 요약하면 Martin Fowler는 올바른 방법을 권장하지만 ORM은 귀하가 그렇게하지 못하게합니다.

밥 마틴이 더 현실적인 제안을하고 있다고 생각 합니다 . 그는 DTO에 논리가 없는지에 대해 이야기합니다. 그들은 단순히 데이터를 유지하고 훨씬 더 객체 지향적이고 DTO를 직접 사용하지 않는 다른 계층으로 전송합니다. 이것은 누출 된 추상화가 당신을 물지 못하게합니다. DTO와 DTO 자체가있는 계층은 OO가 아닙니다. 그러나 일단 그 계층에서 벗어나면 Martin Fowler가 옹호하는 것처럼 OO가됩니다.

이 분리의 이점은 지속성 계층을 추상화하는 것입니다. JPA에서 JDBC로 (또는 그 반대로) 전환 할 수 있으며 비즈니스 로직을 변경할 필요가 없습니다. 그것은 단지 DTO에 달려 있으며, DTO가 어떻게 채워지 는지 상관하지 않습니다 .

주제 를 약간 변경 하려면 SQL 데이터베이스가 객체 지향적이지 않다는 사실을 고려해야합니다. 그러나 ORM에는 일반적으로 테이블 당 개체 (개체)가 있습니다. 처음부터 이미 전투에서 패배했습니다. 내 경험상, 객체 지향 방식으로 원하는 방식으로 엔티티를 표현할 수 없습니다.

"에 관해서는 서비스"밥 마틴은라는 이름의 클래스를 가진에 대한 것입니다 . 그것은 객체 지향이 아닙니다. 서비스는 무엇을합니까? 관련된 모든 것 . 레이블이 붙어있을 수도 있습니다 . 나는 그가 서비스 계층 (더 나은 이름은 비즈니스 로직 계층 일 것이다)을 옹호한다고 생각하지만 그 계층의 모든 클래스는 의미있는 이름을 가질 것이다. FooBarServiceFooBarsFooBarUtils


2
ORM에 대한 귀하의 요점에 동의하십시오. 실제로 엔터티가 여러 테이블에 저장 될 수있는 경우 엔터티를 직접 DB에 매핑한다는 거짓말을 전파합니다.
Andy

@Daniel Kaplan Bob Martin 비디오의 업데이트 된 링크가 무엇인지 알고 있습니까?
Brian Morearty

25

저는 현재 그린 필드 프로젝트를 진행하고 있으며 어제 아키텍처 결정을 거의하지 않았습니다. 재미있게도 나는 'Enterprise Application Architecture의 패턴'의 몇 장을 다시 방문해야했습니다.

이것이 우리가 생각 해낸 것입니다.

  • 데이터 레이어. 데이터베이스를 쿼리하고 업데이트합니다. 층은 주사 가능한 저장소를 통해 노출된다.
  • 도메인 계층. 비즈니스 로직이있는 곳입니다. 이 계층은 주입 가능한 리포지토리를 사용하며 대부분의 비즈니스 논리를 담당합니다. 이것이 우리가 철저히 테스트 할 응용 프로그램의 핵심입니다.
  • 서비스 계층. 이 계층은 도메인 계층과 통신하고 클라이언트 요청을 처리합니다. 우리의 경우 서비스 계층은 매우 간단합니다. 요청을 도메인 계층으로 전달하고 보안을 처리하고 다른 크로스 커팅 문제를 처리합니다. 이것은 MVC 응용 프로그램의 컨트롤러와 크게 다르지 않습니다. 컨트롤러는 작고 간단합니다.
  • 클라이언트 계층. SOAP를 통해 서비스 계층과 통신합니다.

우리는 다음과 같이 끝납니다.

클라이언트-> 서비스-> 도메인-> 데이터

클라이언트, 서비스 또는 데이터 계층을 합리적인 양의 작업으로 대체 할 수 있습니다. 도메인 논리가 서비스에 존재하고 서비스 계층을 교체하거나 제거하기로 결정한 경우 모든 비즈니스 논리를 다른 곳으로 이동해야합니다. 이러한 요구 사항은 드물지만 발생할 수 있습니다.

이 모든 것을 말하면서, 이것은 이것이 Martin Fowler가 말한 의미와 거의 비슷하다고 생각합니다.

이러한 서비스는 도메인 모델 위에 있으며 도메인 모델을 데이터에 사용합니다.

아래 다이어그램은 이것을 잘 보여줍니다.

http://martinfowler.com/eaaCatalog/serviceLayer.html

http://martinfowler.com/eaaCatalog/ServiceLayerSketch.gif


2
어제 SOAP를 결정 하셨나요? 이것이 요구 사항입니까, 아니면 더 나은 아이디어가 없었습니까?
JensG

1
REST는 요구 사항을 충족시키지 않습니다. SOAP 또는 REST는 대답에 아무런 영향을 미치지 않습니다. 내 이해에서 서비스는 도메인 논리의 관문입니다.
CodeART

게이트웨이에 절대적으로 동의하십시오. SOAP는 (표준화 된) bloatware이므로 물어봐야합니다. 그렇습니다. 질문 / 응답에도 영향을 미치지 않습니다.
JensG

6
호의를 베풀고 서비스 계층을 중단하십시오. UI는 도메인을 직접 사용해야합니다. 나는 이것을 전에 보았고 당신의 도메인은 항상 풍부한 모델이 아닌 빈혈 dtos가됩니다.
Andy

이 슬라이드는 서비스 계층과이 그래픽에 대한 설명을 매우 깔끔하게 설명합니다. slideshare.net/ShwetaGhate2/…
Marc Juchli

9

이것은 실제로 유스 케이스에 의존하는 것 중 하나입니다. 서비스 계층의 전반적인 요점은 비즈니스 로직을 함께 통합하는 것입니다. 이는 여러 컨트롤러가 실제로 결제 수행 방식에 신경 쓰지 않고 동일한 UserService.MakeHimPay ()를 호출 할 수 있음을 의미합니다. 서비스에서 진행되는 작업은 객체 속성을 수정하는 것만 큼 간단하거나 다른 서비스를 처리하는 복잡한 논리 (예 : 타사 서비스 호출, 유효성 검사 논리 호출 또는 단순히 데이터베이스에 무언가 저장)를 수행하는 것일 수 있습니다. )

그렇다고 도메인 개체에서 모든 논리를 제거 할 필요는 없습니다. 때로는 도메인 객체에 대한 메소드가 자체적으로 계산을 수행하는 것이 더 합리적입니다. 마지막 예에서 서비스는 리포지토리 / 도메인 개체에 대한 중복 계층입니다. 요구 사항 변경에 대한 훌륭한 버퍼를 제공하지만 실제로는 필요하지 않습니다. 서비스가 필요하다고 생각되면 도메인 개체 대신 간단한 "개체 Y에서 속성 X 수정"논리를 수행하십시오. 도메인 클래스의 논리는 getter / setter를 통해 모든 필드를 노출시키지 않고 "필드에서이 값 계산"에 빠지는 경향이 있습니다.


2
비즈니스 로직을 갖춘 서비스 계층을 선호하는 입장은 많은 의미가 있지만 여전히 몇 가지 의문이 남아 있습니다. 필자의 글에서 나는 서비스 계층에 대해 비즈니스 로직의 빈 공간으로 말하는 몇 가지 존경 할만한 출처를 인용했다. 이것은 귀하의 답변과 직접 대조되며, 아마도이 차이를 분명히 할 수 있습니까?
Jeroen Vannevel 5

5
나는 그것이 비즈니스 로직의 유형과 사용되는 언어와 같은 다른 요소에 실제로 달려 있음을 발견했습니다. 일부 비즈니스 로직은 도메인 객체에 잘 맞지 않습니다. 한 가지 예는 데이터베이스에서 결과를 가져온 후 결과를 필터링 / 정렬하는 것입니다. 비즈니스 논리이지만 도메인 개체에는 의미가 없습니다. 서비스가 간단한 논리에 사용되거나 도메인의 결과 및 논리를 변환하는 데 가장 유용하게 사용되므로 데이터 저장 또는 개체의 데이터 계산을 처리 할 때 가장 유용합니다.
firelore

8

프로그래머가 도메인 논리를 도메인 개체에 넣는 것을 피하는 이유를 설명하는 가장 쉬운 방법은 대개 "어떻게 유효성 검사 논리를 배치해야합니까?"라는 상황에 직면하는 것입니다. 이 도메인 객체를 예로 들어 보겠습니다.

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            if(value < 0) throw new ArgumentOutOfRangeException("value");
            this.someProperty = value;
        }
    }
}

따라서 우리는 setter에 몇 가지 기본 유효성 검사 논리가 있습니다 (음수 일 수 없음). 문제는이 논리를 실제로 재사용 할 수 없다는 것입니다. 화면이나 ViewModel 또는 Controller가 실제로 도메인 객체에 대한 변경 사항을 커밋 하기 전에 유효성 검사를 수행 해야하는 화면이 있습니다. 그리고 . setter를 호출 할 때 예외를 테스트하는 것은 트랜잭션을 시작하기 전에 모든 유효성 검사를 실제로 수행했기 때문에 추악한 해킹입니다.

그렇기 때문에 사람들이 유효성 검사 논리를와 같은 일종의 서비스로 옮기는 이유 MyEntityValidator입니다. 그런 다음 엔터티와 호출 논리는 모두 유효성 검사 서비스에 대한 참조를 가져 와서 재사용 할 수 있습니다.

당신이 경우 하지 않는 그렇게하고 당신은 여전히에 다시 사용하는 검증 논리를 원하는, 당신은 엔티티 클래스의 정적 메서드에 넣어 결국 :

public class MyEntity
{
    private int someProperty = 0;

    public int SomeProperty
    {
        get { return this.someProperty; }
        set
        {
            string message;
            if(!TryValidateSomeProperty(value, out message)) 
            {
                throw new ArgumentOutOfRangeException("value", message);
            }
            this.someProperty = value;
        }
    }

    public static bool TryValidateSomeProperty(int value, out string message)
    {
        if(value < 0)
        {
            message = "Some Property cannot be negative.";
            return false;
        }
        message = string.Empty;
        return true;
    }
}

이렇게하면 도메인 모델의 "애매 성"이 줄어들고 속성 옆에 유효성 검사 논리가 유지됩니다.하지만 정적 메서드를 좋아하는 사람은 없다고 생각합니다.


1
그렇다면 귀하의 의견에 가장 적합한 검증 솔루션은 무엇입니까?
Flashrunner

3
@Flashrunner-유효성 검사 논리는 비즈니스 논리 계층에 결정적이지만 엔터티 및 데이터베이스 계층에도 복제됩니다. 비즈니스 계층은 사용자 등에 게 정보를 제공하여 잘 처리하지만 다른 계층은 오류 / 예외를 발생시키고 프로그래머 (자신)가 데이터를 손상시키는 실수를 저 지르지 못하게합니다.
Scott Whitlock

6

Martin Fowler의 Anemic Domain Model article 을 읽으면 대답이 분명하다고 생각합니다 .

도메인 모델에서 비즈니스 로직 인 도메인을 제거하는 것은 본질적으로 객체 지향 디자인을 깨뜨리고 있습니다.

가장 기본적인 객체 지향 개념 인 객체와 데이터를 캡슐화합니다. 예를 들어, 계정을 닫는 것은 계정 개체가 자체적으로 수행해야하는 작업입니다. 따라서 서비스 계층이 해당 작업을 수행하도록하는 것은 객체 지향 솔루션이 아닙니다. 그것은 절차 적이며 마틴 파울러가 빈혈 도메인 모델에 대해 이야기 할 때 언급 한 것입니다.

서비스 계층이 계정 개체 자체를 닫는 대신 계정을 닫는 경우 실제 계정 개체가없는 것입니다. 귀하의 계정 "개체"는 단지 데이터 구조 일뿐입니다. 마틴 파울러 (Martin Fowler)가 제안한 것처럼, 결국에는 게터와 세터가 든 가방이 있습니다.


1
편집했습니다. 나는 이것이 실제로 매우 유용한 설명을 찾았고 그것이 공감할 가치가 있다고 생각하지 않습니다.
BadHorsie

1
풍부한 모델의 단점 중 하나가 있습니다. 그리고 그것은 모델과 약간 관련된 것을 끌어내는 개발자입니다. 개설 / 폐쇄 상태는 계정의 속성입니까? 주인은 어때? 그리고 은행? 계정에서 모두 참조해야합니까? 은행에 문의하거나 계좌를 통해 직접 계좌를 폐쇄 하시겠습니까? 빈혈 모델의 경우 이러한 연결은 모델의 고유 한 부분이 아니라 다른 클래스 (서비스 또는 관리자라고 함)에서 해당 모델로 작업 할 때 생성됩니다.
Hubert Grzeskowiak

4

서비스 계층에서 비즈니스 로직을 어떻게 구현 하시겠습니까? 사용자로부터 결제 할 때 단순히 속성에서 가치를 공제하는 것이 아니라 결제를 만드는 것입니다.

결제 방법으로 결제 레코드를 생성하고 해당 사용자의 부채를 추가하고이 모든 것을 리포지토리에 유지해야합니다. 서비스 방법으로이 작업을 수행하는 것은 매우 간단하며 전체 작업을 트랜잭션으로 래핑 할 수도 있습니다. 집계 된 도메인 모델에서 동일한 작업을 수행하는 것이 훨씬 더 문제가됩니다.


2

tl; dr 버전 :
필자의 경험과 의견에 따르면 비즈니스 로직이있는 모든 객체는 도메인 모델의 일부 여야합니다. 데이터 모델에는 논리가 없어야합니다. 서비스는이 둘을 함께 묶어 교차 문제 (데이터베이스, 로깅 등)를 처리해야합니다. 그러나 허용되는 답변 이 가장 실용적입니다.

다른 사람들이 암시 한 더 긴 버전은 "모델"이라는 단어에 대한 말이 있다는 것입니다. 게시물은 데이터 모델과 도메인 모델이 동일한 것처럼 전환되므로 매우 일반적인 실수입니다. "service"라는 단어에 약간의 영향이있을 수도 있습니다.

실제로는 도메인 개체를 변경하는 서비스가 없어야합니다. 그 이유는 서비스가 해당 속성의 값을 변경하기 위해 객체의 모든 속성에 대해 몇 가지 방법을 가지고 있기 때문입니다. 개체에 대한 인터페이스가 있거나없는 경우에도 서비스가 더 이상 공개 폐쇄 원칙을 따르지 않기 때문에 문제가됩니다. 대신, 도메인 대 데이터에 관계없이 모델에 더 많은 데이터를 추가 할 때마다 서비스에 더 많은 기능을 추가해야합니다. 그 주위에는 특정한 방법이 있지만 이것이 "엔터프라이즈"응용 프로그램이 실패하는 가장 일반적인 이유입니다. 특히 "엔터프라이즈"가 "시스템의 모든 객체에 대한 인터페이스를 가지고 있음"을 의미하는 조직에서는 그렇게 생각합니다. 인터페이스에 새로운 메소드를 추가한다고 상상할 수 있습니까? 그런 다음 모델의 단일 속성에 대해 두 개 또는 세 개의 서로 다른 구현 (인앱 구현, 모의 구현 및 디버그 구현, 인 메모리 구성)? 나에게 끔찍한 생각처럼 들린다.

여기에 더 이상 들어 가지 않을 문제가 있지만 요점은 이것입니다. 하드 코어 객체 지향 프로그래밍은 관련 객체 외부의 어느 누구도 객체 내의 속성 값을 변경할 수 없으며 " 개체 내 속성 값을 참조하십시오. 데이터를 읽기 전용으로 만들어서 완화 할 수 있습니다. 많은 사람들이 읽기 전용으로도 데이터를 사용하고 해당 데이터의 유형을 변경해야 할 때와 같은 문제가 계속 발생할 수 있습니다. 모든 소비자가이를 수용하기 위해 변경해야 할 수도 있습니다. 그렇기 때문에 모든 사람과 모든 사람이 API를 사용할 때 공개 또는 보호 속성 / 데이터를 보유하지 않는 것이 좋습니다. 이것이 궁극적으로 OOP가 발명 된 이유입니다.

승인 된 것으로 표시된 답변 외에 대부분의 답변이 문제를 흐리게하고 있다고 생각합니다. 허용 된 것으로 표시된 것은 좋지만, 여전히 글 머리 기호 4가 일반적으로가는 방법이라는 답장과 동의가 필요하다고 느꼈습니다.

DDD에서 서비스는 특별히 집계 루트에 속하지 않는 작업이있는 상황을위한 것입니다. 종종 서비스가 필요할 때 올바른 루트를 사용하지 않았 음을 암시 할 수 있으므로 여기에서주의해야합니다. 그러나 당신이 한 것으로 가정하면, 서비스는 여러 루트에서 작업을 조정하거나 때로는 도메인 모델과 관련이없는 문제를 처리하는 데 사용됩니다 ...


1

대답은 유스 케이스에 따라 다릅니다. 그러나 가장 일반적인 시나리오에서는 서비스 계층에 놓인 비즈니스 논리를 준수합니다. 당신이 제공 한 예는 정말 간단한 것입니다. 그러나 일단 분리 된 시스템 또는 서비스를 생각하고 그 위에 트랜잭션 동작을 추가하기 시작하면 실제로 서비스 계층의 일부로 발생하기를 원합니다.

비즈니스 로직이없는 서비스 계층에 대해 인용 한 소스는 비즈니스 계층 인 다른 계층을 소개합니다. 많은 시나리오에서 서비스 계층과 비즈니스 계층이 하나로 압축됩니다. 실제로 시스템 설계 방법에 따라 다릅니다. 작업을 3 계층으로 처리하고 꾸미고 소음을 계속 추가 할 수 있습니다.

이상적으로 수행 할 수있는 것은 상태를 유지하기 위해 도메인 모델에서 작동하는 비즈니스 논리를 포함하는 모델 서비스입니다 . 가능한 한 서비스를 분리하려고 시도해야합니다.


0

MVC에서 모델은 비즈니스 로직으로 정의됩니다. 그가 MVC를 사용하지 않는 한 다른 곳이어야한다고 주장하는 것은 잘못되었습니다. 서비스 계층을 모듈 시스템과 유사하게 봅니다. 관련 기능 세트를 멋진 패키지로 묶을 수 있습니다. 해당 서비스 계층의 내부에는 사용자와 동일한 작업을 수행하는 모델이 있습니다.

이 모델은 응용 프로그램 데이터, 비즈니스 규칙, 논리 및 기능으로 구성됩니다. http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller


0

서비스 계층의 개념은 DDD 관점에서 볼 수 있습니다. Aaronaught는 그의 답변에서 그것을 언급했습니다.

일반적인 접근 방식은 클라이언트 유형에 고유 한 컨트롤러를 사용하는 것입니다. 웹 브라우저 일 수도 있고 다른 응용 프로그램 일 수도 있고 기능 테스트 일 수도 있습니다. 요청 및 응답 형식이 다를 수 있습니다. 따라서 응용 프로그램 서비스를 6 각형 아키텍처 를 활용하기위한 도구로 사용합니다 . 구체적인 요청에 맞는 인프라 클래스를 주입합니다. 예를 들어, 웹 브라우저 요청을 제공하는 컨트롤러는 다음과 같습니다.

class WebBroserController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new PayPalClient(),
                new OrderRepository()
            )
        ;

        $response = new HtmlView($responseData);
    }
}

기능 테스트를 작성하는 경우 가짜 결제 클라이언트를 사용하고 싶을 것입니다 .html 응답이 필요하지 않을 것입니다. 내 컨트롤러는 다음과 같습니다.

class FunctionalTestController
{
    public function purchaseOrder()
    {
        $data = $_POST;

        $responseData =
            new PurchaseOrderApplicationService(
                new FakePayPalClient(),
                new OrderRepository(),
                $data
            )
        ;

        return new JsonData($responseData);
    }
}

따라서 응용 프로그램 서비스는 business-logic을 실행하기 위해 설정 한 환경입니다. 인프라 구현을 불가지론 적으로 모델 클래스라고합니다.

따라서 이러한 관점에서 질문에 대답하십시오.

단순히 컨트롤러에서 로직을 추출하여 대신 서비스에 넣는 수단입니까?

아니.

컨트롤러와 도메인간에 계약을 맺어야합니까?

음, 그렇게 부를 수 있습니다.

도메인과 서비스 계층 사이에 계층이 있어야합니까?

아니.


모든 종류의 서비스 사용을 완전히 거부하지만 근본적으로 다른 접근 방식이 있습니다. 예를 들어, David West는 자신의 저서 Object Thinking 에서 모든 물체가 작업을 수행하는 데 필요한 모든 자원을 가지고 있어야한다고 주장합니다. 이 접근 방식은 예를 들어 ORM을 삭제합니다 .


-2

기록을 위해.

SRP :

  1. Model = Data, 여기 setter와 getter가 있습니다.
  2. 논리 / 서비스 = 여기서 결정을 내립니다.
  3. 리포지토리 / DAO = 여기서 영구적으로 정보를 저장하거나 검색합니다.

이 경우 다음 단계를 수행해도됩니다.

부채가 계산을 요구하지 않는 경우 :

userObject.Debt = 9999;

그러나 계산이 필요한 경우 :

userObject.Debt= UserService.CalculateDebt(userObject)

또는

UserService.UpdateDebt(userObject)

또한 계산이 지속성 계층에서 수행되면 이러한 저장 절차가 수행됩니다.

UserRepository.UpdateDebt(userObject)

이 경우 데이터베이스에서 사용자를 검색하고 부채를 업데이트하려면 몇 단계 (사실 두 단계)로 수행해야하며 서비스 기능으로 랩 / 캡슐화 할 필요가 없습니다.

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)

저장이 필요한 경우 세 번째 단계를 추가 할 수 있습니다

User userObject=UserRepository.GetUserByName(somename);
UserService.UpdateDebt(userObject)
UserRepository.Save(userobject);

제안 된 솔루션에 대하여

a) 우리는 엔드 개발자가 함수에 캡슐화하는 대신 몇 개를 작성하도록 두려운 것을 두려워해서는 안됩니다.

b) 그리고 인터페이스에 관해, 일부 개발자들은 인터페이스를 좋아하고 몇 가지 경우에는 전혀 필요하지 않습니다.

c) 서비스의 목표는 주로 공유 / 정적 기능을 사용할 수 있기 때문에 속성없이 서비스를 생성하는 것입니다. 단위 테스트도 쉽습니다.


“비즈니스 로직은 모델이 아닌 서비스에 있어야합니까?”
gnat

3
어떤 종류의 문장이 "We shouldn't be afraid to left the end-developer to write a couple of instead of encapsulate it in a function."? 나는 루이스 블랙 만 인용 할 수 있습니다."내 말이 아니었다면 그 해에 대학에서 보내지 않았을 것입니다. "
Malachi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.