도메인 모델에 대한 유효성 검사를 수행해야하는 위치


38

여전히 도메인 모델 유효성 검사에 대한 모범 사례를 찾고 있습니다. 도메인 모델의 생성자에 유효성 검사를 넣는 것이 좋습니까? 내 도메인 모델 유효성 검사 예제는 다음과 같습니다.

public class Order
 {
    private readonly List<OrderLine> _lineItems;

    public virtual Customer Customer { get; private set; }
    public virtual DateTime OrderDate { get; private set; }
    public virtual decimal OrderTotal { get; private set; }

    public Order (Customer customer)
    {
        if (customer == null)
            throw new  ArgumentException("Customer name must be defined");

        Customer = customer;
        OrderDate = DateTime.Now;
        _lineItems = new List<LineItem>();
    }

    public void AddOderLine //....
    public IEnumerable<OrderLine> AddOderLine { get {return _lineItems;} }
}


public class OrderLine
{
    public virtual Order Order { get; set; }
    public virtual Product Product { get; set; }
    public virtual int Quantity { get; set; }
    public virtual decimal UnitPrice { get; set; }

    public OrderLine(Order order, int quantity, Product product)
    {
        if (order == null)
            throw new  ArgumentException("Order name must be defined");
        if (quantity <= 0)
            throw new  ArgumentException("Quantity must be greater than zero");
        if (product == null)
            throw new  ArgumentException("Product name must be defined");

        Order = order;
        Quantity = quantity;
        Product = product;
    }
}

모든 제안에 감사드립니다.

답변:


47

Martin Fowler흥미로운 기사에서 대부분의 사람들 (나를 포함하여)이 간과하는 양상을 강조합니다.

그러나 내가 끊임없이 사람들을 여행한다고 생각하는 한 가지는 isValid 메소드와 같은 컨텍스트 독립적 인 방법으로 객체의 유효성을 생각할 때입니다.

유효성 검사를 컨텍스트에 바인딩 된 것으로 생각하는 것이 훨씬 더 유용하다고 생각합니다. 일반적으로 수행하려는 작업입니다. 이 주문을 작성하는 것이 유효합니까?이 고객이 호텔에 체크인 할 수 있습니까? 따라서 isValid와 같은 메소드가 아니라 isValidForCheckIn과 같은 메소드가 있습니다.

이것으로부터 생성자는 유효성 검사를 수행 해서는 안됩니다 . 모든 컨텍스트에서 공유하는 매우 기본적인 위생 검사일 수도 있습니다.

기사에서 다시 :

얼굴 소개에서 Alan Cooper는 유효한 상태에 대한 아이디어로 인해 사용자가 불완전한 정보를 입력 (및 저장)하지 못하게해서는 안된다고 주장했습니다. 지미 닐슨 (Jimmy Nilsson)이 작업하고있는 책의 초안을 읽을 때 며칠 전이 생각났다. 그는 개체에 오류가 있어도 항상 개체를 저장할 수 있어야한다는 원칙을 발표했습니다. 이것이 절대적인 규칙이어야한다고 확신하지는 않지만, 사람들이 원하는 것보다 더 많이 저축하는 것을 막는 경향이 있다고 생각합니다. 유효성 검사의 컨텍스트를 생각하면이를 방지하는 데 도움이 될 수 있습니다.


누군가 고맙습니다. 데이터가 90 %이지만 아무것도 저장하지 않는 양식은 사용자에게 불공평합니다. 데이터를 잃지 않기 위해 다른 10 %를 구성하는 경우가 많으므로 모든 확인 작업은 시스템이 10 %를 추적하지 못하도록합니다. 만들어졌다. 백엔드에서도 비슷한 문제가 발생할 수 있습니다 (예 : 데이터 가져 오기). 일반적으로 유효하지 않은 데이터로 올바르게 작업하는 것이 데이터가 발생하는 것을 방지하는 것보다 낫습니다.
psr

2
@psr 데이터가 유지되지 않으면 백엔드 로직이 필요합니까? 데이터가 비즈니스 모델에 의미가없는 경우 모든 조작을 클라이언트 측에 남겨 둘 수 있습니다. 데이터가 의미가 없다면 메시지를주고받을 수있는 리소스가 낭비 될 수 있습니다 (클라이언트-서버). 따라서 "도메인 개체를 유효하지 않은 상태로 만들 수 없습니다!"라는 아이디어로 돌아갑니다. .
Geo C.

2
왜 그렇게 모호한 답변에 대한 투표가 그렇게 많은지 궁금합니다. DDD를 사용할 때 일부 데이터가 INT인지 또는 범위 내에 있는지 단순히 확인하는 규칙이있는 경우가 있습니다. 예를 들어 앱 사용자가 제품에 대한 일부 제약 조건을 선택하도록 허용 한 경우 (누군가가 내 제품을 몇 번이나 미리 볼 수 있는지, 한 달 간격으로 몇 일 간격으로) 여기서 두 제약 조건은 모두 int 여야하며 그 중 하나는 0-31 범위에 있어야합니다. 이것은 DDD가 아닌 환경에서 서비스 나 컨트롤러에 적합한 데이터 형식 유효성 검사 인 것 같습니다. 그러나 DDD에서는 도메인 (90 %)에서 validaion을 유지하는 편입니다.
Geo C.

2
도메인을 유효한 상태로 유지하기 위해 도메인에 대해 상위 계층에 너무 많은 정보를 제공하도록하면 나쁜 디자인이 나쁜 것 같습니다. 도메인은 유효한 상태를 보장하는 도메인이어야합니다. 상위 계층의 숄더에서 너무 많이 이동하면 도메인이 빈혈이되고 비즈니스에 해를 끼칠 수있는 불가피한 제약 조건이 사라질 수 있습니다. 내가 지금 아는 것은 적절한 일반화로 가능한 한 지속성에 가깝거나 데이터 조작 코드에 가깝게 유효성을 유지하는 것입니다 (최종 상태에 도달하도록 조작 할 때).
Geo C.

추신 : 나는 형식 유효성 검사와 함께 인증 (무엇을 할 수 있음), 인증 (메시지가 올바른 위치에서 왔거나 올바른 클라이언트가 보낸 것이 아닌가)를 형식 유효성 검사와 함께 사용하지 않습니다. 또는 비즈니스 규칙. 90 %라고 말하면 대부분 비즈니스 규칙에도 형식 유효성 검사가 포함되어 있다는 것을 의미합니다. 물론 형식 유효성 검사는 상위 계층에있을 수 있지만 대부분 도메인에 있습니다 (EmailAddress 값 개체에서 유효성 검사되는 전자 메일 주소 형식).
Geo C.

5

이 질문이 조금 오래되었다는 사실에도 불구하고 가치있는 것을 추가하고 싶습니다.

@MichaelBorgwardt에 동의하고 테스트 가능성을 제기하여 확장하고 싶습니다. "레거시 코드로 효과적으로 작업하기"에서 Michael Feathers는 테스트에 대한 장애물에 대해 많은 이야기를하며 이러한 장애물 중 하나는 객체를 "구축하기 어렵다"입니다. 유효하지 않은 객체를 구성 할 수 있어야하며 Fowler가 제안한대로 상황에 따른 유효성 검사에서 이러한 조건을 식별 할 수 있어야합니다. 테스트 하네스에서 객체를 구성하는 방법을 알 수 없다면 클래스 테스트에 어려움이있을 것입니다.

유효성에 관해서는 제어 시스템을 생각하고 싶습니다. 제어 시스템은 출력 상태를 지속적으로 분석하고 출력이 설정 점에서 벗어날 때 수정 조치를 적용하여 작동합니다.이를 폐쇄 루프 제어라고합니다. 폐쇄 루프 제어는 본질적으로 편차를 예상하고이를 수정하는 역할을하며 실제 환경이 작동하는 방식이므로 모든 실제 제어 시스템은 일반적으로 폐쇄 루프 컨트롤러를 사용합니다.

상황에 따른 유효성 검사와 객체 구성이 쉬워 시스템을 쉽게 작업 할 수 있다고 생각합니다.


1
많은 경우 객체는 구성하기가 어려워 보입니다. 예를 들어이 경우 테스트중인 클래스에서 상속되는 래퍼 클래스를 만들어 공용 생성자를 무시하고 잘못된 상태에서 기본 개체의 인스턴스를 만들 수 있습니다. 이것은 클래스와 생성자에 올바른 액세스 수정자를 사용하는 것이 적절하며 잘못 사용하면 테스트에 해로울 수 있습니다. 또한 적절한 경우를 제외하고 "밀봉 된"클래스와 메소드를 피하면 코드를 더 쉽게 테스트 할 수 있습니다.
P. Roe

4

이미 알고 계시 겠지만 ...

객체 지향 프로그래밍에서 클래스의 생성자 (때로는 ctor로 단축)는 객체 생성시 호출되는 특수한 유형의 서브 루틴입니다. 새 객체를 사용할 수 있도록 준비하며, 생성자가 객체를 처음 만들 때 필요한 멤버 변수를 설정하는 데 사용하는 매개 변수를 사용하는 경우가 많습니다. 클래스의 데이터 멤버 값을 구성하기 때문에 생성자라고합니다.

c'tor 매개 변수로 전달 된 데이터의 유효성을 검사 하는 것은 생성자에서 확실히 유효합니다. 그렇지 않으면 유효하지 않은 오브젝트를 생성 할 수 있습니다.

그러나 (그리고 이것은 단지 내 의견이며, 현재로서는 좋은 문서를 찾을 수 없습니다.)-데이터 유효성 검사가 복잡한 작업 (예 : 비동기 작업-데스크탑 응용 프로그램을 개발하는 경우 서버 기반 유효성 검사)이 필요한 경우 더 좋습니다 어떤 종류의 초기화 또는 명시 적 유효성 검사 함수에 넣고 멤버 null는 c'tor에서 기본값 (예 :)으로 설정됩니다.


또한 코드 샘플에 포함 된대로 참고 사항처럼 ...

에서 추가 유효성 검사 (또는 다른 기능)를 수행하지 않는 한 AddOrderLine대부분 은 정면 역할을 List<LineItem>하지 않고 속성으로 노출 될 것 입니다.Order


왜 컨테이너를 노출합니까? 컨테이너가 무엇인지 상위 계층에 중요한 것은 무엇입니까? 방법이있는 것이 완벽 AddLineItem합니다. 실제로 DDD의 경우이 방법이 선호됩니다. 경우 List<LineItem>A의 의존 사용자 정의 컬렉션 개체, 다음 노출 된 재산과 모든 것을 변화 List<LineItem>특성 변화, 오류 및 예외의 적용을받습니다.
IAbstract

4

검증은 가능한 빨리 수행해야합니다.

도메인 모델 또는 기타 소프트웨어 작성 방법과 같은 모든 컨텍스트에서 유효성 검사는 유효성을 검사하려는 목적과 현재 수준에 부합해야합니다.

귀하의 질문에 따르면, 대답은 유효성 검사를 분할하는 것입니다.

  1. 속성 유효성 검사는 해당 속성의 값이 올바른지 확인합니다 (예 : 1-10 사이의 범위가 초과 된 경우).

  2. 객체 검증은 객체의 모든 속성이 서로 연계되어 유효 함을 보장합니다. 예를 들어 BeginDate는 EndDate 이전입니다. 데이터 저장소에서 값을 읽고 BeginDate와 EndDate가 기본적으로 DateTime.Min으로 초기화되었다고 가정합니다. BeginDate를 설정하는 경우 "ETD는 반드시 종료해야합니다"규칙을 적용 할 이유가 없습니다. 이는 YET을 적용하지 않기 때문입니다. 모든 속성을 설정 한 후에이 규칙을 확인해야합니다. 이것은 루트 수준에서 호출 할 수 있습니다

  3. 집계 (또는 집계 루트) 엔터티에 대해서도 유효성 검사를 수행해야합니다. Order 객체는 유효한 데이터를 포함 할 수 있으며 OrderLines도 마찬가지입니다. 그러나 비즈니스 규칙에 따르면 1,000 달러를 초과하는 주문은 없을 것입니다. 경우에 따라이 규칙이 어떻게 적용됩니까? "양을 확인하지 않음"속성을 추가 할 수는 없습니다. 남용으로 이어질 수 있기 때문입니다 (더 이상, 심지어는 심지어 "이 불쾌한 요청"을 막을 수도 있습니다).

  4. 다음으로 프레젠테이션 레이어에서 유효성 검사가 수행됩니다. 실패 할 것임을 알면서 네트워크를 통해 객체를 전송할 예정입니까? 또는 사용자 에게이 부담을 아끼고 유효하지 않은 값을 입력하자마자 알려주십시오. 예를 들어 대부분의 경우 DEV 환경이 프로덕션보다 느립니다. "아직 또 다른 테스트 실행 중에이 필드를 다시 잊어 버렸습니다"라는 정보를 받기 전에 30 초 동안 기다리시겠습니까? 특히 보스가 목을들이 마시면서 수정해야하는 프로덕션 버그가있는 경우?

  5. 지속성 수준의 유효성 검사는 속성 값 유효성 검사와 최대한 비슷해야합니다. 이렇게하면 모든 종류 또는 일반 오래된 데이터 판독기의 매퍼를 사용할 때 "널"또는 "잘못된 값"오류를 읽는 예외를 방지 할 수 있습니다. 저장 프로 시저를 사용하면이 문제가 해결되지만 동일한 유효성 검사 논리 AGAIN을 작성하고 AGAIN을 실행해야합니다. 저장 프로시 저는 DB 관리 도메인이므로 HIS 작업도 수행하려고하지 마십시오.

유명한 단어로 "그것은 달려있다"고 말하지만, 이제는 왜 그것이 의존하는지 알고 있습니다.

나는이 모든 것을 한곳에 둘 수 있기를 원하지만 불행히도 이것은 할 수 없습니다. 이렇게하면 모든 레이어에 대한 모든 유효성 검사가 포함 된 "God 개체"에 종속됩니다. 당신은 그 어두운 길을 가고 싶지 않습니다.

이러한 이유로 속성 예외 만 유효성 검사 예외를 throw합니다. 다른 모든 수준은 IsValid 메서드와 함께 ValidationResult를 사용하여 모든 "손상된 규칙"을 수집하고 단일 AggregateException으로 사용자에게 전달합니다.

호출 스택을 전파 할 때 프리젠 테이션 레이어에 도달 할 때까지 AggregateExceptions에서 다시 수집합니다. WCF의 경우 FaultException으로 서비스 계층에서이 예외를 클라이언트에 직접 전달할 수 있습니다.

이를 통해 예외를 가져 와서 각 입력 컨트롤에서 개별 오류를 표시하도록 분할하거나 단일화 목록으로 표시 할 수 있습니다. 선택은 당신입니다.

그렇기 때문에 프레젠테이션 유효성 검사를 언급하여 가능한 한 단축했습니다.

내가 왜 집계 수준 (또는 원하는 경우 서비스 수준)에서 유효성 검사를 받아야하는지 궁금해하는 경우, 향후 누가 내 서비스를 사용할지 알려주는 수정 구슬이 없기 때문입니다. 잘못된 데이터를 입력하여 다른 사람이 실수를 저 지르지 못하도록 자신의 실수를 찾는 데 어려움을 겪을 수 있습니다. 버그가있을 때 누가 먼저 물어봐? 응용 프로그램 B의 관리자는 사용자에게 "최종에 오류가 없으며 데이터를 제공하는 것"이라고 기꺼이 알려줍니다.

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