Xác nhận và ủy quyền trong kiến ​​trúc lớp


13

Tôi biết bạn đang suy nghĩ (hoặc có thể la hét), "không phải là một câu hỏi khác hỏi xác thực thuộc về kiến ​​trúc lớp nào?!?" Vâng, vâng, nhưng hy vọng điều này sẽ có một chút khác biệt về chủ đề này.

Tôi là một người tin tưởng vững chắc rằng xác nhận có nhiều hình thức, dựa trên ngữ cảnh và thay đổi theo từng cấp độ của kiến ​​trúc. Đó là cơ sở cho bài đăng - giúp xác định loại xác nhận nào sẽ được thực hiện trong mỗi lớp. Ngoài ra, một câu hỏi thường được đưa ra là nơi kiểm tra ủy quyền thuộc về.

Kịch bản ví dụ xuất phát từ một ứng dụng cho một doanh nghiệp phục vụ ăn uống. Định kỳ trong ngày, tài xế có thể chuyển đến văn phòng bất kỳ khoản tiền thừa nào họ đã tích lũy trong khi đưa xe tải từ nơi này sang nơi khác. Ứng dụng cho phép người dùng ghi lại 'giọt tiền' bằng cách thu thập ID tài xế và số tiền. Đây là một số mã bộ xương để minh họa các lớp liên quan:

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
        ...
    }
}

Tôi đã chỉ ra 10 vị trí mà tôi đã thấy kiểm tra xác thực được đặt trong mã. Câu hỏi của tôi là những gì kiểm tra bạn sẽ, nếu có, sẽ được thực hiện tại mỗi quy tắc kinh doanh nhất định sau (cùng với kiểm tra tiêu chuẩn về độ dài, phạm vi, định dạng, loại, v.v.):

  1. Số tiền giảm phải lớn hơn 0.
  2. Việc thả tiền mặt phải có Tài xế hợp lệ.
  3. Người dùng hiện tại phải được ủy quyền để thêm tiền mặt (người dùng hiện tại không phải là tài xế).

Vui lòng chia sẻ suy nghĩ của bạn, cách bạn có hoặc sẽ tiếp cận kịch bản này và lý do cho sự lựa chọn của bạn.


SE không chính xác là nền tảng phù hợp để "thúc đẩy một cuộc thảo luận lý thuyết và chủ quan". Bỏ phiếu để đóng.
tdammers

Tuyên bố kém. Tôi thực sự đang tìm kiếm các thực hành tốt nhất.
SonOfPirate

2
@tdammers - Vâng, đó là nơi thích hợp. Ít nhất nó muốn được. Từ FAQ: 'Câu hỏi chủ quan được cho phép.' Đó là lý do tại sao họ tạo trang web này thay vì Stack Overflow. Đừng là một phát xít gần. Nếu câu hỏi hút, nó sẽ mờ dần đi.
FastAl

@FastAI: Đó không phải là phần 'chủ quan', mà là phần 'thảo luận' làm phiền tôi.
tdammers

Tôi nghĩ rằng bạn có thể tận dụng các đối tượng giá trị ở đây bằng cách có một CashDropAmountđối tượng giá trị thay vì sử dụng a Decimal. Kiểm tra xem trình điều khiển có tồn tại hay không sẽ được thực hiện trong trình xử lý lệnh và tương tự với các quy tắc ủy quyền. Bạn có thể nhận được ủy quyền miễn phí bằng cách làm một cái gì đó giống như Approver approver = approverService.findById(employeeId)nơi nó ném nếu nhân viên không ở trong vai trò phê duyệt. Approversẽ chỉ là một đối tượng giá trị, không phải là một thực thể. Bạn cũng có thể thoát khỏi nhà máy của mình hoặc sử dụng phương pháp nhà máy trên AR thay thế : cashDrop = driver.dropCash(...).
plalx

Câu trả lời:


2

Tôi đồng ý rằng những gì bạn đang xác nhận sẽ khác nhau trong mỗi lớp của ứng dụng. Tôi thường chỉ xác nhận những gì được yêu cầu để thực thi mã trong phương thức hiện tại. Tôi cố gắng coi các thành phần cơ bản là hộp đen và không xác nhận dựa trên cách các thành phần đó được thực hiện.

Vì vậy, như một ví dụ, trong lớp CashDropApi của bạn, tôi sẽ chỉ xác minh rằng 'hợp đồng' không có giá trị. Điều này ngăn chặn NullReferenceExceptions và là tất cả những gì cần thiết để đảm bảo phương thức này thực thi đúng.

Tôi không biết rằng tôi xác nhận bất cứ điều gì trong các lớp dịch vụ hoặc lệnh và trình xử lý sẽ chỉ xác minh rằng 'lệnh' không rỗng vì những lý do tương tự như trong lớp CashDropApi. Tôi đã thấy (và thực hiện) xác nhận cả hai cách wrt cho các lớp thực vật và nhà máy. Một hoặc khác là nơi bạn muốn xác thực giá trị của 'số tiền' và các tham số khác không phải là null (quy tắc kinh doanh của bạn).

Kho lưu trữ chỉ nên xác thực rằng dữ liệu chứa trong đối tượng phù hợp với lược đồ được xác định trong cơ sở dữ liệu của bạn và thao tác daa sẽ thành công. Ví dụ: nếu bạn có một cột không thể rỗng hoặc có độ dài tối đa, v.v.

Đối với kiểm tra an ninh, tôi nghĩ rằng nó thực sự là một câu hỏi về ý định. Vì quy tắc này nhằm ngăn chặn truy cập trái phép, tôi muốn thực hiện kiểm tra này càng sớm càng tốt để giảm số bước không cần thiết tôi đã thực hiện nếu người dùng không được ủy quyền. Có lẽ tôi đã đặt nó vào CashDropApi.


1

Quy tắc kinh doanh đầu tiên của bạn

Số tiền giảm phải lớn hơn 0.

trông giống như một bất biến của CashDropthực thể và của AddCashDropCommandlớp bạn . Có một vài cách mà tôi thực thi một bất biến như thế này:

  1. Thực hiện Thiết kế theo lộ trình Hợp đồng và sử dụng Hợp đồng mã với sự kết hợp của Điều kiện tiên quyết, Hậu điều kiện và [ContractInvariantMethod] tùy theo trường hợp của bạn.
  2. Viết mã rõ ràng trong hàm tạo / setters ném ra một ArgumentException nếu bạn vượt qua với số tiền nhỏ hơn 0.

Quy tắc thứ hai của bạn có bản chất rộng hơn (theo các chi tiết trong câu hỏi): không hợp lệ có nghĩa là thực thể Trình điều khiển có cờ cho biết họ có thể lái xe (tức là không có giấy phép lái xe bị treo), điều đó có nghĩa là người lái xe đã thực sự làm việc vào ngày hôm đó hoặc có nghĩa đơn giản là driverId, được chuyển đến CashDropApi, là hợp lệ trong cửa hàng lưu trữ lâu bền.

Trong bất kỳ trường hợp nào trong số này, bạn sẽ cần điều hướng mô hình miền của mình và lấy Driverví dụ từ bạn IEmployeeRepository, như bạn làm trong location 4ví dụ mã của mình. Vì vậy, ở đây bạn cần đảm bảo rằng lệnh gọi đến kho lưu trữ không trả về null, trong trường hợp đó driverId của bạn không hợp lệ và bạn không thể tiếp tục xử lý thêm nữa.

Đối với 2 kiểm tra khác (giả thuyết của tôi) (tài xế có giấy phép lái xe hợp lệ không, là tài xế làm việc ngày hôm nay), bạn đang thực hiện các quy tắc kinh doanh.

Những gì tôi có xu hướng làm ở đây là sử dụng một tập hợp các lớp trình xác nhận hoạt động trên các thực thể (giống như mẫu đặc tả từ cuốn sách của Eric Evans - Thiết kế hướng tên miền). Tôi đã sử dụng FluentValidation để xây dựng các quy tắc và trình xác nhận này. Sau đó tôi có thể soạn (và do đó sử dụng lại) các quy tắc phức tạp hơn / hoàn chỉnh hơn từ các quy tắc đơn giản hơn. Và tôi có thể quyết định lớp nào trong kiến ​​trúc của mình để chạy chúng. Nhưng tôi có tất cả chúng được mã hóa ở một nơi, không nằm rải rác trên hệ thống.

Quy tắc thứ ba của bạn liên quan đến mối quan tâm xuyên suốt: ủy quyền. Vì bạn đã sử dụng bộ chứa IoC (giả sử rằng bộ chứa IoC của bạn hỗ trợ chặn phương thức), bạn có thể thực hiện một số AOP . Viết một apsect thực hiện ủy quyền và bạn có thể sử dụng bộ chứa IoC của mình để thực hiện hành vi ủy quyền này ở nơi cần thiết. Chiến thắng lớn ở đây là bạn đã viết logic một lần, nhưng bạn có thể sử dụng lại nó trên hệ thống của mình.

Để sử dụng đánh chặn thông qua proxy động (Castle Windsor, Spring.NET, Ninject 3.0, v.v.), lớp mục tiêu của bạn cần triển khai giao diện hoặc kế thừa từ lớp cơ sở. Bạn sẽ chặn trước cuộc gọi đến phương thức đích, kiểm tra ủy quyền của người dùng và ngăn cuộc gọi tiếp tục với phương thức thực tế (ném đoạn trích, ghi nhật ký, trả về giá trị cho biết lỗi hoặc thứ gì khác) nếu người dùng không có vai trò đúng để thực hiện các hoạt động.

Trong trường hợp của bạn, bạn có thể chặn cuộc gọi đến một trong hai

CashDropService.AddCashDrop(...) 

AddCashDropCommandHandler.Handle(...)

Các vấn đề ở đây có lẽ CashDropServicekhông thể bị chặn bởi vì không có lớp giao diện / lớp cơ sở. Hoặc AddCashDropCommandHandlerkhông được tạo bởi IoC của bạn, do đó IoC của bạn không thể tạo proxy động để chặn cuộc gọi. Spring.NET có một tính năng hữu ích trong đó bạn có thể nhắm mục tiêu một phương thức trên một lớp trong một cụm thông qua một biểu thức chính quy, vì vậy điều này có thể hoạt động.

Hy vọng điều này cung cấp cho bạn một số ý tưởng.


Bạn có thể giải thích làm thế nào tôi "sử dụng bộ chứa IoC của bạn để thực hiện hành vi ủy quyền này ở nơi cần thiết" không? Điều này nghe có vẻ hấp dẫn nhưng để AOP và IoC hợp tác thoát khỏi tôi cho đến nay.
SonOfPirate

Đối với phần còn lại, tôi đồng ý với việc đặt xác thực trong hàm tạo và / hoặc setters để ngăn đối tượng vào trạng thái không hợp lệ (xử lý bất biến). Nhưng ngoài điều đó và một tham chiếu đến kiểm tra null sau khi đi đến IEmployeeRep repository để tìm trình điều khiển, bạn không cung cấp bất kỳ chi tiết nào mà bạn sẽ thực hiện phần còn lại của xác thực. Với việc sử dụng FluentValidation và tái sử dụng, vv nó cung cấp, bạn sẽ áp dụng các quy tắc trong mô hình đã cho ở đâu?
SonOfPirate

Tôi đã chỉnh sửa câu trả lời của mình - xem nếu điều này giúp. Đối với "bạn sẽ áp dụng các quy tắc trong mô hình đã cho ở đâu?"; có lẽ khoảng 4, 5, 6, 7 trong trình xử lý lệnh của bạn. Bạn có quyền truy cập vào kho lưu trữ có thể mang lại thông tin bạn cần để thực hiện xác nhận cấp độ kinh doanh. Nhưng tôi nghĩ rằng có những người khác sẽ không đồng ý với tôi ở đây.
RobertMS

Để làm rõ, tất cả các phụ thuộc đang được tiêm. Tôi bỏ nó đi để giữ cho mã tham chiếu ngắn gọn. Yêu cầu của tôi có liên quan nhiều hơn đến việc có một sự phụ thuộc trong khía cạnh vì các khía cạnh không được tiêm qua container. Vì vậy, làm thế nào để AuthorizationAspect có được một tham chiếu đến AuthorizationService chẳng hạn?
SonOfPirate

1

Đối với các quy tắc:

1- Số tiền giảm phải lớn hơn 0.

2- Việc giảm tiền mặt phải có Tài xế hợp lệ.

3- Người dùng hiện tại phải được ủy quyền để thêm tiền mặt (người dùng hiện tại không phải là tài xế).

Tôi sẽ xác thực vị trí (1) cho quy tắc kinh doanh (1) và đảm bảo Id không rỗng hoặc âm (giả sử số 0 là hợp lệ) làm kiểm tra trước cho quy tắc (2). Lý do là quy tắc của tôi về "Đừng vượt qua ranh giới lớp với dữ liệu sai mà bạn có thể kiểm tra với thông tin có sẵn". Một ngoại lệ cho điều này sẽ là nếu dịch vụ thực hiện xác nhận như là một phần nhiệm vụ của nó đối với những người gọi khác. Trong trường hợp đó, sẽ chỉ đủ để xác nhận ở đó.

Đối với quy tắc (2) và (3), điều này phải được thực hiện tại lớp truy cập cơ sở dữ liệu (hoặc chính lớp db) vì nó liên quan đến truy cập db. Không cần phải đi lại giữa các lớp có chủ ý.

Cụ thể có thể tránh quy tắc (3) nếu chúng ta để GUI ngăn người dùng trái phép ấn nút kích hoạt kịch bản này. Trong khi điều này khó mã hóa hơn, nó là tốt hơn.

Câu hỏi hay!


+1 cho ủy quyền - đưa nó vào UI là một cách thay thế mà tôi không đề cập đến trong câu trả lời của mình.
RobertMS

Mặc dù việc kiểm tra ủy quyền trong giao diện người dùng cung cấp trải nghiệm tương tác nhiều hơn cho người dùng, tôi đang phát triển API dựa trên dịch vụ và không thể đưa ra bất kỳ giả định nào về những quy tắc mà người gọi đã hoặc chưa thực hiện. Đó là bởi vì rất nhiều trong số các kiểm tra này có thể dễ dàng được ủy quyền cho UI mà tôi đã chọn sử dụng dự án API làm cơ sở cho bài đăng. Tôi đang tìm kiếm các thực tiễn tốt nhất thay vì sách giáo khoa nhanh chóng và dễ dàng.
SonOfPirate

@SonOfPirate, INMO, UI cần thực hiện xác nhận vì nó nhanh hơn và nó có nhiều dữ liệu hơn dịch vụ (trong một số trường hợp). Bây giờ dịch vụ không nên gửi dữ liệu ra ngoài ranh giới của nó mà không thực hiện xác nhận của riêng mình vì đây là một phần trách nhiệm của nó miễn là bạn muốn dịch vụ không tin tưởng khách hàng. Theo đó, tôi đề nghị kiểm tra phi db được thực hiện trong dịch vụ (một lần nữa) trước khi gửi dữ liệu tới cơ sở dữ liệu để xử lý thêm.
NoChance
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.