programing

단일 책임 원칙 대 빈혈 도메인 모델 반 패턴

nasanasas 2020. 12. 13. 09:42
반응형

단일 책임 원칙 대 빈혈 도메인 모델 반 패턴


저는 Single Responsibility Principle을 매우 진지하게 받아들이는 프로젝트에 참여하고 있습니다. 소규모 수업이 많고 모든 것이 매우 간단합니다. 그러나 빈혈 도메인 모델이 있습니다. 모델 클래스에는 동작이 없으며 속성 가방 일뿐입니다. 이것은 우리 디자인에 대한 불만이 아닙니다. 실제로 꽤 잘 작동하는 것 같습니다.

설계 검토 중에 SRP는 시스템에 새로운 동작이 추가 될 때마다 나오므로 일반적으로 새로운 동작은 새로운 클래스로 끝납니다. 이것은 일을 매우 쉽게 단위 테스트 할 수있게 해주지 만, 나는 그것이 관련된 곳에서 행동을 끌어내는 것처럼 느껴지기 때문에 때때로 당황 스럽습니다.

SRP를 올바르게 적용하는 방법에 대한 이해를 높이려고 노력하고 있습니다. SRP는 하나의 객체에 동일한 컨텍스트를 공유하는 비즈니스 모델링 동작을 추가하는 것에 반대하는 것 같습니다. 객체는 필연적으로 하나 이상의 관련 작업을 수행하거나 하나의 작업을 수행하지만 모양을 변경하는 여러 비즈니스 규칙을 알고 있기 때문입니다. 출력의.

그렇다면 최종 결과는 Anemic Domain Model 인 것처럼 느껴집니다. 이는 확실히 우리 프로젝트의 경우입니다. 그러나 빈혈 도메인 모델은 반 패턴입니다.

이 두 가지 아이디어가 공존 할 수 있습니까?

편집 : 몇 가지 컨텍스트 관련 링크 :

SRP - http://www.objectmentor.com/resources/articles/srp.pdf
빈혈 도메인 모델 - http://martinfowler.com/bliki/AnemicDomainModel.html

저는 선지자를 찾고 그들이 말하는 것을 복음으로 따르는 것을 좋아하는 개발자가 아닙니다. 그래서 나는 두 개념의 정의의 소스로서 "이것들이 규칙이다"라고 말하는 방법으로 이것들에 대한 링크를 제공하지 않습니다.


"예"라고 말해야하지만 SRP를 제대로 수행해야합니다. 같은 작업이 하나의 클래스에만 적용된다면 그 클래스에 속하지 않겠습니까? 동일한 작업이 여러 클래스에 적용된다면 어떨까요? 이 경우 데이터와 동작을 결합하는 OO 모델을 따르려면 작업을 기본 클래스에 넣습니다.

설명에 따르면 기본적으로 작업 가방 인 클래스로 끝나는 것으로 생각되므로 기본적으로 C 스타일의 코딩 인 구조체와 모듈을 다시 만들었습니다.

링크 된 SRP 문서에서 : " SRP는 가장 간단한 원칙 중 하나이며 올바르게 이해하기 가장 어려운 것 중 하나입니다. "


RDM (Rich Domain Model)과 SRP (Single Responsibility Principle)가 반드시 상충되는 것은 아닙니다. RDM은 "데이터 빈 + 컨트롤러 클래스의 모든 비즈니스 로직"(DBABLICC)을 옹호하는 모델 인 SRP의 매우 전문화 된 하위 클래스와 더 많이 충돌합니다.

Martin의 SRP 장 을 읽으면 그의 모뎀 예제가 전적으로 도메인 계층에 있지만 DataChannel 및 Connection 개념을 별도의 클래스로 추상화하는 것을 볼 수 있습니다. 그는 모뎀 자체를 래퍼로 유지합니다. 이는 클라이언트 코드에 유용한 추상화이기 때문입니다. 단순한 레이어링 보다 적절한 (리) 팩토링에 관한입니다. 응집력과 결합은 여전히 ​​설계의 기본 원칙입니다.

마지막으로 세 가지 문제 :

  • Martin이 스스로 지적했듯이 다양한 '변화 이유'를 보는 것이 항상 쉬운 것은 아닙니다. YAGNI, Agile 등의 바로 그 개념은 미래의 변화 이유에 대한 예상을 방해하므로 당장 명확하지 않은 이유를 발명해서는 안됩니다. 나는 '조기, 예상되는 변경 사유'를 SRP 적용 실질적인 위험 으로보고 개발자가 관리해야합니다.

  • 이전에 더하여 SRP의 올바른 (그러나 불필요한 항문 ) 적용 조차도 원치 않는 복잡성을 초래할 수 있습니다. 항상 당신의 클래스를 유지해야하는 다음 가난한 무리에 대해 생각하십시오. 사소한 동작을 자체 인터페이스, 기본 클래스 및 한 줄 구현으로 부지런히 추상화하면 단순히 단일 클래스 여야하는 것이 무엇인지 이해하는 데 도움이 될까요?

  • 소프트웨어 설계는 종종 경쟁 세력간에 최상의 타협을 얻는 것입니다. 예를 들어, 계층화 된 아키텍처는 대부분 SRP의 좋은 응용 프로그램이지만, 예를 들어 비즈니스 클래스의 속성을 부울 에서 열거 형 으로 변경하면 모든 계층에 파급 효과가 있다는 사실은 어떻 습니까? -db에서 도메인, 파사드, 웹 서비스, GUI로? 이것이 잘못된 디자인을 가리키는가? 반드시 그런 것은 아닙니다. 디자인이 변경의 한 측면을 다른 측면에 선호한다는 사실을 나타냅니다.


SRP 논문의 인용문은 매우 정확합니다. SRP는 제대로하기가 어렵습니다. 이 하나와 OCP는 실제로 프로젝트를 완료하기 위해 최소한 어느 정도 완화되어야하는 SOLID의 두 가지 요소입니다. 둘 중 하나를 과도하게 적용하면 매우 빠르게 라비올리 코드가 생성됩니다.

"변경 이유"가 너무 구체적이면 SRP는 실제로 터무니없는 길이로 걸릴 수 있습니다. POCO / POJO "데이터 백"도 변경되는 필드 유형을 "변경"으로 간주하면 SRP를 위반하는 것으로 간주 할 수 있습니다. 필드의 유형 변경이 "변경"에 필요한 허용이라고 상식적으로 말할 것이라고 생각할 수 있지만, 기본 제공 값 유형에 대한 래퍼가있는 도메인 레이어를 보았습니다. ADM을 Utopia처럼 보이게하는 지옥.

가독성이나 원하는 응집력 수준에 따라 현실적인 목표를 세우는 것이 좋습니다. 당신이 "이 수업이 한 가지를하기를 원합니다"라고 말할 때, 그것을하기 위해 필요한 것보다 더 많거나 적지 않아야합니다. 이 기본 철학으로 최소한 절차 적 응집력을 유지할 수 있습니다. "I want this class to maintain all the data for a invoice"는 일반적으로 어떤 필드에 대해 정확하고 내부적으로 일관된 값을 제공하는 방법을 알고있는 개체의 책임에 따라 소계를 합산하거나 판매 세를 계산하는 일부 비즈니스 로직을 허용합니다. 포함합니다.

저는 개인적으로 "경량"도메인에 큰 문제가 없습니다. "데이터 전문가"의 역할 하나만 있으면 도메인 개체가 클래스와 관련된 모든 필드 / 속성뿐 아니라 모든 계산 된 필드 논리, 명시 적 / 암시 적 데이터 유형 변환 및 가능한 더 간단한 유효성 검사 규칙의 키퍼가됩니다. (즉, 필수 필드, 값 제한, 허용되는 경우 내부적으로 인스턴스를 중단시키는 것). 가중 평균 또는 롤링 평균에 대한 계산 알고리즘이 변경 될 가능성이있는 경우 알고리즘을 캡슐화하고 계산 된 필드에서 참조합니다 (좋은 OCP / PV).

나는 그러한 도메인 객체를 "빈혈"이라고 생각하지 않습니다. 이 용어에 대한 나의 인식은 "데이터 백"으로, 외부 세계에 대한 개념이 전혀 없거나 필드가 포함되어있는 것 이외의 필드 간의 관계도없는 필드 모음입니다. 나도 그것을 보았고, 객체가 문제라는 것을 결코 알지 못했던 객체 상태의 불일치를 추적하는 것은 재미가 없습니다. 지나치게 열성적인 SRP는 데이터 개체가 비즈니스 로직에 대한 책임이 없다고 말함으로써이를 이끌어 낼 수 있지만 일반적으로 상식이 먼저 개입하여 데이터 전문가로서 개체가 일관된 내부 상태를 유지해야한다고 말합니다.

다시 말씀 드리지만, 저는 Active Record보다 Repository 패턴을 선호합니다. 하나의 책임을 가진 하나의 객체, 그리고 그 계층 위에있는 시스템의 다른 어떤 것도 작동 방식에 대해 알아야 할 것이 거의 없습니다. Active Record를 사용하려면 도메인 계층이 지속성 방법 또는 프레임 워크 (각 클래스를 읽고 쓰는 데 사용되는 저장 프로 시저의 이름, 프레임 워크 별 개체 참조 또는 ORM 정보로 필드를 장식하는 속성)에 대한 특정 세부 정보를 적어도 알아야합니다. ), 따라서 기본적으로 모든 도메인 클래스에 변경해야하는 두 번째 이유를 주입합니다.

내 $ 0.02.


견고한 원칙을 따르면 실제로 DDD의 풍부한 도메인 모델에서 멀어 졌다는 사실을 발견했습니다. 결국에는 신경 쓰지 않는다는 것을 알았습니다. 요점은 도메인 모델의 논리적 개념과 어떤 언어로 된 클래스가 어떤 종류의 파사드에 대해 이야기하지 않는 한 1 : 1로 매핑되지 않았 음을 발견했습니다.

이것이 구조체와 모듈이있는 C 스타일의 프로그래밍이라고는 말할 수 없지만 오히려 더 기능적인 것으로 끝날 것입니다. 스타일은 비슷하지만 세부 사항이 큰 차이를 만듭니다. 내 클래스 인스턴스가 고차 함수, 부분 함수 응용 프로그램, 느리게 평가 된 함수 또는 위의 일부 조합처럼 작동하는 것을 발견했습니다. 그것은 나에게 다소 비현실적이지만 TDD + SOLID를 따르는 코드를 작성하여 얻은 느낌입니다. 결국 하이브리드 OO / Functional 스타일처럼 행동했습니다.

상속이 나쁜 단어라는 점은 상속이 Java / C #과 같은 언어에서 충분히 세분화되지 않았기 때문이라고 생각합니다. 다른 언어에서는 문제가 적고 더 유용합니다.


SRP의 정의는 다음과 같습니다.

"클래스는 변경해야 할 비즈니스 이유가 하나뿐입니다."

따라서 행동을 하나의 "비즈니스 이유"로 그룹화 할 수있는 한 동일한 클래스에서 공존하지 않을 이유가 없습니다. 물론 "비즈니스 이유"를 정의하는 것은 토론의 여지가 있습니다 (모든 이해 관계자가 토론해야 함).


내가 호언 장담을하기 전에, 여기에 한마디로 내 의견이 있습니다. 어딘가에 모든 것이 합쳐 져야하고 ... 그리고 강이 그것을 통과합니다.

나는 코딩에 시달린다.

=======

빈혈 데이터 모델과 나 ... 글쎄, 우리는 많이 친해. 비즈니스 로직이 거의 내장되지 않은 중소 규모 애플리케이션의 특성 일 수 있습니다. 어쩌면 내가 조금 지각 일 수도 있습니다.

그러나 여기 내 2 센트가 있습니다.

엔티티의 코드를 제외하고 인터페이스에 묶을 수는 없습니까?

public class Object1
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    private IAction1 action1;

    public Object1(IAction1 action1)
    {
        this.action1 = action1;
    }

    public void DoAction1()
    {
        action1.Do(Property1);
    }
}

public interface IAction1
{
    void Do(string input1);
}

Does this somehow violate the principles of SRP?

Furthermore, isn't having a bunch of classes sitting around not tied to each other by anything but the consuming code actually a larger violation of SRP, but pushed up a layer?

Imagine the guy writing the client code sitting there trying to figure out how to do something related to Object1. If he has to work with your model he will be working with Object1, the data bag, and a bunch of 'services' each with a single responsibility. It'll be his job to make sure all those things interact properly. So now his code becomes a transaction script, and that script will itself contain every responsibility necessary to properly complete that particular transaction (or unit of work).

Furthermore, you could say, "no brah, all he needs to do is access the service layer. It's like Object1Service.DoActionX(Object1). Piece of cake." Well then, where's the logic now? All in that one method? Your still just pushing code around, and no matter what, you'll end up with data and the logic being separated.

So in this scenario, why not expose to the client code that particular Object1Service and have it's DoActionX() basically just be another hook for your domain model? By this I mean:

public class Object1Service
{
    private Object1Repository repository;

    public  Object1Service(Object1Repository repository)
    {
        this.repository = repository;
    }

    // Tie in your Unit of Work Aspect'ing stuff or whatever if need be
    public void DoAction1(Object1DTO object1DTO)
    {
        Object1 object1 = repository.GetById(object1DTO.Id);
        object1.DoAction1();
        repository.Save(object1);
    }
}

You still have factored out the actual code for Action1 from Object1 but for all intensive purposes, have a non-anemic Object1.

Say you need Action1 to represent 2 (or more) different operations that you would like to make atomic and separated into their own classes. Just create an interface for each atomic operation and hook it up inside of DoAction1.

That's how I might approach this situation. But then again, I don't really know what SRP is all about.


Convert your plain domain objects to ActiveRecord pattern with a common base class to all domain objects. Put common behaviour in the base class and override the behaviour in derived classes wherever necessary or define the new behaviour wherever required.

참고URL : https://stackoverflow.com/questions/1399027/single-responsibility-principle-vs-anemic-domain-model-anti-pattern

반응형