계층 구조의 검증 및 권한 부여


13

"당신은 계층 구조에서 유효성 검사가 어디에 있는지 묻는 또 다른 질문이 아닌가?" 글쎄요, 그러나 이것은 주제에 대해 조금 다른 생각이 될 수 있기를 바랍니다.

유효성 검사는 다양한 형식을 취하고 상황에 따라 다르며 아키텍처의 각 수준에 따라 다릅니다. 이것이 포스트의 기초입니다-각 계층에서 어떤 유형의 검증이 수행되어야하는지 식별하는 데 도움이됩니다. 또한 권한 검사가 속하는 곳이 자주 제기됩니다.

예제 시나리오는 케이터링 비즈니스를위한 애플리케이션에서 비롯됩니다. 주간에는 운전자가 트럭을 현장에서 현장으로 가져가는 동안 누적 된 초과 현금이 사무실로 들어올 수 있습니다. 이 응용 프로그램을 통해 사용자는 운전자 ID와 금액을 수집하여 '현금 하락'을 기록 할 수 있습니다. 관련 레이어를 설명하기위한 일부 스켈레톤 코드는 다음과 같습니다.

public class CashDropApi  // This is in the Service Facade Layer
{
    [WebInvoke(Method = "POST")]
    public void AddCashDrop(NewCashDropContract contract)
    {
        // 1
        Service.AddCashDrop(contract.Amount, contract.DriverId);
    }
}

public class CashDropService  // This is the Application Service in the Domain Layer
{
    public void AddCashDrop(Decimal amount, Int32 driverId)
    {
        // 2
        CommandBus.Send(new AddCashDropCommand(amount, driverId));
    }
}

internal class AddCashDropCommand  // This is a command object in Domain Layer
{
    public AddCashDropCommand(Decimal amount, Int32 driverId)
    {
        // 3
        Amount = amount;
        DriverId = driverId;
    }

    public Decimal Amount { get; private set; }
    public Int32 DriverId { get; private set; }
}

internal class AddCashDropCommandHandler : IHandle<AddCashDropCommand>
{
    internal ICashDropFactory Factory { get; set; }       // Set by IoC container
    internal ICashDropRepository CashDrops { get; set; }  // Set by IoC container
    internal IEmployeeRepository Employees { get; set; }  // Set by IoC container

    public void Handle(AddCashDropCommand command)
    {
        // 4
        var driver = Employees.GetById(command.DriverId);
        // 5
        var authorizedBy = CurrentUser as Employee;
        // 6
        var cashDrop = Factory.CreateCashDrop(command.Amount, driver, authorizedBy);
        // 7
        CashDrops.Add(cashDrop);
    }
}

public class CashDropFactory
{
    public CashDrop CreateCashDrop(Decimal amount, Employee driver, Employee authorizedBy)
    {
        // 8
        return new CashDrop(amount, driver, authorizedBy, DateTime.Now);
    }
}

public class CashDrop  // The domain object (entity)
{
    public CashDrop(Decimal amount, Employee driver, Employee authorizedBy, DateTime at)
    {
        // 9
        ...
    }
}

public class CashDropRepository // The implementation is in the Data Access Layer
{
    public void Add(CashDrop item)
    {
        // 10
        ...
    }
}

코드에서 유효성 검사가 수행 된 위치를 10 곳 표시했습니다. 내 질문은 다음과 같은 비즈니스 규칙 (길이, 범위, 형식, 유형 등에 대한 표준 검사와 함께)에서 수행 할 수있는 검사입니다.

  1. 현금 인출 액은 0보다 커야합니다.
  2. 현금 인출에는 유효한 운전자가 있어야합니다.
  3. 현재 사용자는 현금 인출을 추가 할 권한이 있어야합니다 (현재 사용자는 드라이버가 아님).

당신의 생각, 당신이이 시나리오를 가지고 있거나 어떻게 접근 할 것인지, 그리고 선택 이유를 공유하십시오.


SE는 "이론적이고 주관적인 토론을 장려하는"정확한 플랫폼이 아닙니다. 마감 투표.
tdammers

말로 표현이 잘못되었습니다. 모범 사례를 찾고 있습니다.
SonOfPirate

2
@ tdammers-맞습니다. 최소한되고 싶다. FAQ에서 : '주관적인 질문이 허용됩니다.' 그래서 스택 오버플로 대신이 사이트를 만들었습니다. 가까운 나치가되지 마십시오. 질문이 짜증 나면 모호해집니다.
FastAl

@FASTAI : 그것은 '주관적인'부분이 아니라 나를 괴롭히는 '토론'입니다.
tdammers

CashDropAmount사용하는 대신 값 객체를 사용하여 값 객체 를 활용할 수 있다고 생각합니다 Decimal. 드라이버가 존재하는지 여부를 확인하는 것은 명령 핸들러에서 수행되며 권한 부여 규칙도 마찬가지입니다. Approver approver = approverService.findById(employeeId)직원이 승인자 역할을 수행하지 않는 경우 던지는 위치 와 같은 작업을 수행하여 무료로 승인을받을 수 있습니다. Approver엔티티가 아닌 가치 객체 일뿐입니다. 팩토리를 제거하거나 대신 AR에서 팩토리 메소드를 사용할 수도 있습니다 cashDrop = driver.dropCash(...).
plalx

답변:


2

본인이 확인하는 내용이 신청서의 각 계층마다 다를 것에 동의합니다. 나는 일반적으로 현재 메소드에서 코드를 실행하는 데 필요한 것을 검증합니다. 기본 구성 요소를 블랙 박스로 취급하려고하며 해당 구성 요소가 어떻게 구현되는지에 따라 유효성을 검사하지 않습니다.

예를 들어, CashDropApi 클래스에서 '계약'이 null이 아닌지 확인합니다. 이렇게하면 NullReferenceExceptions이 방지 되고이 메소드가 올바르게 실행되도록하는 데 필요한 전부입니다.

서비스 또는 명령 클래스에서 아무것도 확인하지 않았으며 처리기는 CashDropApi 클래스와 동일한 이유로 'command'가 null이 아닌지 확인합니다. 나는 팩토리와 엔티티 클래스에 대한 두 가지 방법으로 유효성 검사를 보았습니다. 하나 또는 다른 하나는 '금액'값의 유효성을 검사하려는 곳이며 다른 매개 변수는 null이 아닙니다 (비즈니스 규칙).

저장소는 오브젝트에 포함 된 데이터가 데이터베이스에 정의 된 스키마와 일치하고 daa 조작이 성공하는지 검증해야합니다. 예를 들어, 널이 될 수 없거나 최대 길이를 갖는 열이있는 경우

보안 검사에 관해서는 실제로 의도의 문제라고 생각합니다. 이 규칙은 무단 액세스를 방지하기위한 것이기 때문에 사용자에게 권한이없는 경우 수행 한 불필요한 단계 수를 줄이기 위해 프로세스 초기에이 검사를 수행하고 싶습니다. 아마도 CashDropApi에 넣었을 것입니다.


1

첫 비즈니스 규칙

현금 인출 액은 0보다 커야합니다.

당신의 CashDrop실체와 AddCashDropCommand클래스 의 불변 인 것처럼 보입니다 . 다음과 같이 불변을 적용하는 몇 가지 방법이 있습니다.

  1. 계약 별 설계 경로를 사용 하여 사례에 따라 사전 조건, 사후 조건 및 [ContractInvariantMethod]를 조합 하여 코드 계약 을 사용 하십시오.
  2. 0보다 작은 양을 전달하면 ArgumentException을 발생시키는 생성자 / 설정자에 명시 적 코드를 작성하십시오.

두 번째 규칙은 본질적으로 (질문의 세부 사항에 비추어) 더 넓습니다. 유효한 것은 운전자 엔터티가 운전할 수 있음을 나타내는 플래그가 있음을 의미합니다 (즉, 운전 면허증이 정지되지 않았 음). 실제로 그 날에 일하거나 단순히 CashDropApi에 전달 된 driverId가 지속성 저장소에서 유효 함을 의미합니다.

이 경우 코드 예제 에서 와 같이 도메인 모델을 탐색하고 Driver인스턴스를 인스턴스에서 가져와야합니다. 따라서 저장소에 대한 호출이 널을 리턴하지 않는지 확인해야합니다.이 경우 driverId가 유효하지 않으며 더 이상 처리를 진행할 수 없습니다.IEmployeeRepositorylocation 4

다른 2 가지 (가설적인) 점검 (운전자가 유효한 운전 면허증을 가지고 있고, 운전사가 현재 일하고 있었는지)에 대해 비즈니스 규칙을 실행하고 있습니다.

필자가 여기서하는 일은 엔티티에서 작동하는 유효성 검증기 클래스 콜렉션을 사용하는 것입니다 ( Eric Evans 서적-도메인 기반 설계 의 스펙 패턴 과 동일 ). FluentValidation 을 사용 하여 이러한 규칙과 유효성 검사기를 작성했습니다. 그런 다음 간단한 규칙에서 더 복잡하고 완전한 규칙을 작성하여 재사용 할 수 있습니다. 또한 아키텍처에서 실행할 레이어를 결정할 수 있습니다. 그러나 시스템 전체에 흩어져 있지 않은 한곳에서 모두 인코딩했습니다.

세 번째 규칙은 다음과 같은 교차 문제와 관련이 있습니다. 인증. 이미 IoC 컨테이너를 사용하고 있으므로 (IoC 컨테이너가 메소드 차단을 지원한다고 가정) AOP를 수행 할 수 있습니다 . 인증을 수행하는 apsect를 작성하고 IoC 컨테이너를 사용하여 필요한 위치에이 인증 동작을 주입 할 수 있습니다. 여기서 큰 승리는 논리를 한 번 작성했지만 시스템 전체에서 논리를 재사용 할 수 있다는 것입니다.

동적 프록시 (Castle Windsor, Spring.NET, Ninject 3.0 등)를 통해 가로 채기를 사용하려면 대상 클래스가 인터페이스를 구현하거나 기본 클래스에서 상속해야합니다. 대상 메소드에 대한 호출 전에 인터셉트하고 사용자의 권한 부여를 확인하고 사용자가없는 경우 호출이 실제 메소드로 진행되지 않도록합니다 (예외, 로그, 실패를 나타내는 값 리턴 등). 작업을 수행하는 올바른 역할

귀하의 경우에 대한 호출을 가로 챌 수 있습니다

CashDropService.AddCashDrop(...) 

AddCashDropCommandHandler.Handle(...)

CashDropService인터페이스 / 기본 클래스가 없기 때문에 여기에서 문제를 가로 챌 수 없습니다. 또는 AddCashDropCommandHandlerIoC에 의해 생성되지 않으므로 IoC가 통화를 가로 채기 위해 동적 프록시를 생성 할 수 없습니다. Spring.NET에는 정규 표현식을 통해 어셈블리의 클래스에서 메서드를 대상으로 지정할 수있는 유용한 기능이 있으므로 작동 할 수 있습니다.

이것이 당신에게 몇 가지 아이디어를 제공하기를 바랍니다.


"IoC 컨테이너를 사용하여이 권한 부여 동작을 필요한 곳에 주입하는 방법"을 설명 할 수 있습니까? 이것은 매력적으로 들리지만 AOP와 IoC가 함께 작동하게하면 지금까지 탈출하지 못합니다.
SonOfPirate 2016 년

나머지 부분은 객체가 유효하지 않은 상태 (불변량 처리)로 들어 가지 않도록 생성자 및 / 또는 setter에 유효성 검사를 배치하는 데 동의합니다. 그러나 드라이버를 찾기 위해 IEmployeeRepository로 이동 한 후 널 검사에 대한 참조와 나머지 유효성 검사를 수행 할 위치에 대한 세부 정보는 제공하지 않습니다. FluentValidation의 사용과 재사용 등을 고려할 때 주어진 모델에서 규칙을 어디에 적용 하시겠습니까?
SonOfPirate 2016 년

내 답변을 편집했습니다. 이것이 도움이되는지 확인하십시오. "주어진 모델에서 규칙을 어디에 적용 하시겠습니까?"; 아마도 명령 핸들러에서 약 4, 5, 6, 7입니다. 비즈니스 레벨 유효성 검증을 수행하는 데 필요한 정보를 생성 할 수있는 저장소에 액세스 할 수 있습니다. 그러나 나는 여기에 나와 동의하지 않는 다른 사람들이 있다고 생각합니다.
RobertMS

명확히하기 위해 모든 종속성이 주입되고 있습니다. 참조 코드를 간략하게 유지하기 위해 그만 두었습니다. 내 문의는 측면이 컨테이너를 통해 주입되지 않기 때문에 측면 내에서 종속성을 갖는 것과 더 관련이 있습니다. 예를 들어 AuthorizationAspect는 AuthorizationService에 대한 참조를 어떻게 얻습니까?
SonOfPirate

1

규칙의 경우 :

1- 현금 액수는 0보다 커야합니다.

2- 현금 인출에는 유효한 운전자가 있어야합니다.

3- 현재 사용자는 현금 인출을 추가 할 권한이 있어야합니다 (현재 사용자는 드라이버가 아님).

비즈니스 규칙 (1)의 위치 (1)에서 유효성 검사를 수행하고 규칙 (2)의 사전 검사로 ID가 null 또는 음수가 아닌지 (0이 유효하다고 가정) 확인하십시오. 그 이유는 "사용 가능한 정보로 확인할 수있는 잘못된 데이터로 레이어 경계를 넘지 마십시오"라는 규칙입니다. 서비스가 다른 발신자에 대한 의무의 일부로 유효성 검사를 수행하는 경우는 예외입니다. 어떤 경우에는 거기에서만 유효성 검사를하는 것으로 충분합니다.

규칙 (2) 및 (3)의 경우 데이터베이스 액세스 계층 (또는 db 계층 자체)에서 수행해야합니다. 이는 데이터베이스 액세스 계층과 관련이 있기 때문입니다. 의도적으로 레이어 간을 이동할 필요가 없습니다.

GUI를 통해 권한이없는 사용자가이 시나리오를 가능하게하는 버튼을 누르지 못하게하는 경우 특히 규칙 (3)을 피할 수 있습니다. 코딩하기는 어렵지만 더 좋습니다.

좋은 질문!


권한 부여 +1-UI에 넣는 것은 대답에 언급하지 않은 대안입니다.
RobertMS

UI에서 권한 부여 검사를 통해 사용자에게 더 대화 형 경험을 제공하는 동안 서비스 기반 API를 개발 중이며 호출자가 구현 한 규칙이 무엇인지에 대한 가정을 할 수 없습니다. API 프로젝트를 게시물의 기초로 사용하기로 선택한 UI에 이러한 많은 수표를 쉽게 위임 할 수 있기 때문입니다. 나는 교과서보다 빠르고 쉬운 모범 사례를 찾고 있습니다.
SonOfPirate 1

@SonOfPirate, INMO의 UI는 더 빠르며 서비스보다 많은 데이터를 가지고 있기 때문에 유효성 검사를 수행해야합니다 (일부 경우). 이제 서비스가 클라이언트를 신뢰하지 않기를 원한다면 서비스는 자체 검증을 수행하지 않고 데이터를 경계 외부로 보내서는 안됩니다. 따라서 추가 처리를 위해 데이터베이스로 데이터를 보내기 전에 서비스에서 DB 이외의 검사를 다시 수행하는 것이 좋습니다.
NoChance
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.