การตรวจสอบและการอนุญาตในสถาปัตยกรรมแบบเลเยอร์


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. จำนวนเงินที่ฝากเงินสดจะต้องมากกว่าศูนย์
  2. การวางเงินสดจะต้องมีไดรเวอร์ที่ถูกต้อง
  3. ผู้ใช้ปัจจุบันจะต้องได้รับอนุญาตให้เพิ่มเงินสดลดลง (ผู้ใช้ปัจจุบันไม่ใช่ไดรเวอร์)

กรุณาแบ่งปันความคิดของคุณว่าคุณมีหรือจะเข้าใกล้สถานการณ์นี้และเหตุผลในการเลือกของคุณ


SE ไม่ได้เป็นแพลตฟอร์มที่เหมาะสมในการ "ส่งเสริมการอภิปรายเชิงทฤษฎีและเชิงอัตวิสัย" การลงคะแนนให้ปิด
tdammers

คำพูดที่ไม่ดี ฉันกำลังมองหาแนวทางปฏิบัติที่ดีที่สุดจริงๆ
SonOfPirate

2
@tdammers - ใช่แล้วมันเป็นสถานที่ที่เหมาะสม อย่างน้อยก็อยากเป็น จากคำถามที่พบบ่อย: 'อนุญาตให้ใช้คำถามส่วนตัวได้' นั่นเป็นเหตุผลที่พวกเขาสร้างเว็บไซต์นี้แทน Stack Overflow อย่าเป็นนาซีที่ใกล้ชิด หากคำถามแย่ลงมันจะจางหายไป
FastAl

@FastAI: มันไม่ได้เป็นส่วนของ 'อัตนัย' มากนัก แต่เป็นการ 'อภิปราย' ที่ทำให้ฉันรำคาญใจ
tdammers

ผมคิดว่าคุณสามารถใช้ประโยชน์จากค่าวัตถุที่นี่โดยมีวัตถุคุ้มค่ามากกว่าการใช้CashDropAmount Decimalการตรวจสอบว่าไดรเวอร์มีอยู่หรือไม่จะทำในตัวจัดการคำสั่งและสิ่งเดียวกันจะไปสำหรับกฎการอนุญาต คุณสามารถขออนุญาตได้ฟรีโดยทำในสิ่งApprover approver = approverService.findById(employeeId)ที่มันโยนถ้าพนักงานไม่ได้อยู่ในบทบาทผู้อนุมัติ Approverจะเป็นเพียงวัตถุค่าไม่ใช่เอนทิตี นอกจากนี้คุณยังจะได้รับการกำจัดของโรงงานหรือการใช้วิธีการที่โรงงานของคุณบน AR cashDrop = driver.dropCash(...)แทน:
plalx

คำตอบ:


2

ฉันยอมรับว่าสิ่งที่คุณตรวจสอบจะแตกต่างกันในแต่ละชั้นของแอปพลิเคชัน ฉันมักจะตรวจสอบสิ่งที่จำเป็นในการเรียกใช้รหัสในวิธีการปัจจุบัน ฉันพยายามปฏิบัติต่อส่วนประกอบพื้นฐานเป็นกล่องดำและไม่ตรวจสอบความถูกต้องตามวิธีการใช้งานส่วนประกอบเหล่านั้น

ตัวอย่างเช่นในคลาส CashDropApi ของคุณฉันจะตรวจสอบว่า 'สัญญา' ไม่เป็นโมฆะเท่านั้น สิ่งนี้จะป้องกัน NullReferenceExceptions และเป็นสิ่งที่จำเป็นเพื่อให้แน่ใจว่าวิธีการนี้ทำงานอย่างถูกต้อง

ฉันไม่รู้ว่าฉันจะตรวจสอบสิ่งใดในบริการหรือคำสั่งและตัวจัดการจะตรวจสอบว่า 'คำสั่ง' นั้นไม่เป็นโมฆะด้วยเหตุผลเดียวกับในชั้นเรียน CashDropApi ฉันเคยเห็นการตรวจสอบ (และทำ) ทั้งสองวิธี wrt ไปที่โรงงานและชั้นเรียนนิติบุคคล อย่างใดอย่างหนึ่งคือที่ที่คุณต้องการตรวจสอบค่าของ 'จำนวน' และพารามิเตอร์อื่น ๆ จะไม่เป็นโมฆะ (กฎธุรกิจของคุณ)

ที่เก็บควรตรวจสอบว่าข้อมูลที่มีอยู่ในวัตถุนั้นสอดคล้องกับสคีมาที่กำหนดไว้ในฐานข้อมูลของคุณและการดำเนินการ daa จะสำเร็จ ตัวอย่างเช่นหากคุณมีคอลัมน์ที่ไม่สามารถเป็นโมฆะหรือมีความยาวสูงสุด ฯลฯ

สำหรับการตรวจสอบความปลอดภัยฉันคิดว่ามันเป็นคำถามของเจตนา เนื่องจากกฎนี้มีจุดประสงค์เพื่อป้องกันการเข้าถึงโดยไม่ได้รับอนุญาตฉันจึงต้องการตรวจสอบนี้ให้เร็วที่สุดเท่าที่จะทำได้เพื่อลดจำนวนขั้นตอนที่ไม่จำเป็นที่ฉันได้ทำไปหากผู้ใช้ไม่ได้รับอนุญาต ฉันอาจจะใส่ไว้ใน CashDropApi


1

กฎธุรกิจแรกของคุณ

จำนวนเงินที่ฝากเงินสดจะต้องมากกว่าศูนย์

ดูเหมือนว่าค่าคงที่ของCashDropหน่วยงานของคุณและAddCashDropCommandชั้นเรียนของคุณ มีสองวิธีที่ฉันบังคับใช้ค่าคงที่นี้:

  1. ใช้เส้นทาง Design By Contract และใช้Code Contracts ร่วมกับ Preconditions, Postconditions และ [ContractInvariantMethod] โดยขึ้นอยู่กับกรณีของคุณ
  2. เขียนรหัสที่ชัดเจนใน Constructor / setters ที่ส่ง ArgumentException หากคุณส่งผ่านจำนวนที่น้อยกว่า 0

กฎข้อที่สองของคุณนั้นกว้างขึ้น (ตามรายละเอียดในคำถาม): ไม่ถูกต้องหมายความว่าเอนทิตีของคนขับมีธงระบุว่าพวกเขาสามารถขับรถได้ (เช่นไม่มีใบขับขี่ชั่วคราว) หมายความว่าคนขับนั้นถูก จริง ๆ แล้วทำงานวันนั้นหรือหมายความว่าเพียงแค่ driverId ส่งผ่านไปยัง CashDropApi ถูกต้องในการเก็บถาวร

ในกรณีเหล่านี้คุณจะต้องสำรวจโมเดลโดเมนของคุณและรับDriverอินสแตนซ์จากของคุณIEmployeeRepositoryตามที่คุณทำในlocation 4ตัวอย่างโค้ดของคุณ ดังนั้นที่นี่คุณต้องตรวจสอบให้แน่ใจว่าการเรียกไปยังที่เก็บไม่ส่งคืน null ซึ่งในกรณีนี้ driverId ของคุณไม่ถูกต้องและคุณไม่สามารถดำเนินการประมวลผลต่อไปได้อีก

สำหรับการตรวจสอบอีก 2 (สมมุติของฉัน) (คนขับมีใบขับขี่ที่ถูกต้องหรือไม่เป็นคนขับที่ทำงานวันนี้) คุณกำลังดำเนินการตามกฎเกณฑ์ทางธุรกิจ

สิ่งที่ฉันมักจะทำที่นี่คือใช้ชุดของตัวตรวจสอบความถูกต้องที่ทำงานกับเอนทิตี (เช่นเดียวกับรูปแบบข้อมูลจำเพาะจากหนังสือ Eric Evans - การออกแบบโดเมนขับเคลื่อน) ฉันใช้FluentValidationเพื่อสร้างกฎและเครื่องมือตรวจสอบความถูกต้องเหล่านี้ ฉันสามารถเขียน (และนำมาใช้ซ้ำ) กฎที่ซับซ้อนมากขึ้น / สมบูรณ์มากขึ้นจากกฎที่ง่ายขึ้น และฉันสามารถตัดสินใจได้ว่าเลเยอร์ในสถาปัตยกรรมของฉันจะเรียกใช้พวกเขา แต่ฉันได้เข้ารหัสทั้งหมดไว้ในที่เดียวไม่กระจัดกระจายไปทั่วทั้งระบบ

กฎข้อที่สามของคุณเกี่ยวข้องกับข้อกังวลข้าม: การให้สิทธิ์ เนื่องจากคุณใช้คอนเทนเนอร์ IoC อยู่แล้ว (สมมติว่าคอนเทนเนอร์ IoC ของคุณรองรับการสกัดกั้นเมธอด) คุณสามารถทำAOPได้ เขียน apsect ที่ให้สิทธิ์และคุณสามารถใช้คอนเทนเนอร์ IoC ของคุณเพื่อฉีดพฤติกรรมการอนุญาตนี้ที่จำเป็นต้องใช้ ชัยชนะครั้งใหญ่ที่นี่คือคุณได้เขียนตรรกะหนึ่งครั้ง แต่คุณสามารถนำมันกลับมาใช้ใหม่ได้ทั้งระบบ

ในการใช้การสกัดกั้นผ่านพร็อกซีแบบไดนามิก (Castle Windsor, Spring.NET, Ninject 3.0 ฯลฯ ) คลาสเป้าหมายของคุณจำเป็นต้องใช้อินเทอร์เฟซหรือสืบทอดจากคลาสพื้นฐาน คุณจะดักก่อนการเรียกไปยังวิธีการเป้าหมายตรวจสอบการอนุญาตของผู้ใช้และป้องกันการโทรจากการดำเนินการตามวิธีการจริง (โยน excption บันทึกการคืนค่าที่บ่งชี้ความล้มเหลวหรือสิ่งอื่น) ถ้าผู้ใช้ไม่มี บทบาทที่เหมาะสมในการดำเนินการ

ในกรณีของคุณคุณสามารถดักการโทรไปที่ใดก็ได้

CashDropService.AddCashDrop(...) 

AddCashDropCommandHandler.Handle(...)

ปัญหาที่นี่อาจCashDropServiceไม่สามารถดักจับได้เนื่องจากไม่มีคลาสอินเตอร์เฟส / ฐาน หรือAddCashDropCommandHandlerไม่ได้ถูกสร้างขึ้นโดย IoC ของคุณดังนั้น IoC ของคุณไม่สามารถสร้างพร็อกซีแบบไดนามิกเพื่อสกัดกั้นการโทร Spring.NET มีคุณสมบัติที่มีประโยชน์ซึ่งคุณสามารถกำหนดเป้าหมายวิธีในคลาสในแอสเซมบลีผ่าน regex ดังนั้นจึงสามารถใช้งานได้

หวังว่านี่จะช่วยให้คุณมีความคิด


คุณสามารถอธิบายได้อย่างไรว่าฉันจะ "ใช้คอนเทนเนอร์ IoC ของคุณเพื่อฉีดพฤติกรรมการให้สิทธิ์นี้ในที่ที่มันควรจะเป็น"? ฟังดูน่าดึงดูดใจ แต่การได้รับ AOP และ IoC มาทำงานร่วมกันทำให้ฉันรอดพ้นไปได้
SonOfPirate

สำหรับส่วนที่เหลือฉันเห็นด้วยกับการวางการตรวจสอบในตัวสร้างและ / หรือตัวตั้งค่าเพื่อป้องกันไม่ให้วัตถุเข้าสู่สถานะที่ไม่ถูกต้อง (การจัดการค่าคงที่) แต่นอกเหนือจากนั้นและการอ้างอิงถึงการตรวจสอบค่า Null หลังจากไปที่ IEmployeeRepository เพื่อค้นหาไดรเวอร์คุณไม่ได้ให้รายละเอียดใด ๆ ที่คุณจะต้องทำการตรวจสอบส่วนที่เหลือ เมื่อใช้ FluentValidation และการใช้ซ้ำ ฯลฯ จะมีให้คุณจะใช้กฎในรูปแบบที่กำหนดไว้ที่ไหน
SonOfPirate

ฉันได้แก้ไขคำตอบของฉัน - ดูว่าสิ่งนี้จะช่วยได้หรือไม่ สำหรับ "คุณจะใช้กฎในรูปแบบที่กำหนดไว้ที่ไหน"; อาจประมาณ 4, 5, 6, 7 ในตัวจัดการคำสั่งของคุณ คุณสามารถเข้าถึงที่เก็บข้อมูลที่สามารถให้ข้อมูลที่คุณต้องการในการตรวจสอบความถูกต้องระดับธุรกิจ แต่ฉันคิดว่ามีคนอื่นที่ไม่เห็นด้วยกับฉันที่นี่
RobertMS

เพื่อชี้แจงชี้แจงการพึ่งพาทั้งหมดจะถูกฉีด ฉันปล่อยให้ปิดไว้เพื่อให้รหัสอ้างอิงสั้น ๆ คำถามของฉันเกี่ยวข้องกับการพึ่งพาในแง่มุมมากกว่าเนื่องจากไม่มีการแทรกผ่านทางคอนเทนเนอร์ ดังนั้น AuthorizationAspect จะได้รับการอ้างอิงถึง AuthorizationService ได้อย่างไร?
SonOfPirate

1

สำหรับกฎ:

1- ยอดเงินที่ต้องจ่ายจะต้องมากกว่าศูนย์

2- การลดเงินสดจะต้องมีไดรเวอร์ที่ถูกต้อง

3- ผู้ใช้ปัจจุบันจะต้องได้รับอนุญาตให้เพิ่มเงินสดลดลง (ผู้ใช้ปัจจุบันไม่ใช่ไดรเวอร์)

ฉันจะตรวจสอบความถูกต้องในตำแหน่ง (1) สำหรับกฎทางธุรกิจ (1) และตรวจสอบให้แน่ใจว่ารหัสไม่เป็นโมฆะหรือเชิงลบ เหตุผลก็คือกฎของฉัน "อย่าข้ามขอบเขตเลเยอร์ด้วยข้อมูลที่ไม่ถูกต้องที่คุณสามารถตรวจสอบด้วยข้อมูลที่มี" ข้อยกเว้นนี้จะเกิดขึ้นหากบริการทำการตรวจสอบความถูกต้องเป็นส่วนหนึ่งของหน้าที่ต่อผู้โทรรายอื่น ในกรณีนี้มันจะเพียงพอที่จะมีการตรวจสอบที่นั่นเท่านั้น

สำหรับกฎ (2) และ (3) จะต้องทำที่ชั้นการเข้าถึงฐานข้อมูล (หรือชั้นของตัวเอง) เท่านั้นเนื่องจากเกี่ยวข้องกับการเข้าถึงฐานข้อมูล ไม่จำเป็นต้องเดินทางระหว่างเลเยอร์โดยเจตนา

โดยเฉพาะอย่างยิ่งกฎ (3) สามารถหลีกเลี่ยงได้หากเราให้ GUI ป้องกันผู้ใช้ที่ไม่ได้รับอนุญาตกดปุ่มเพื่อเปิดใช้งานสถานการณ์นี้ ในขณะที่รหัสนี้ยากกว่า แต่ก็ยังดีกว่า

คำถามที่ดี!


+1 สำหรับการให้สิทธิ์ - การใส่ไว้ใน UI เป็นทางเลือกที่ฉันไม่ได้กล่าวถึงในคำตอบ
RobertMS

ในขณะที่มีการตรวจสอบการอนุญาตใน UI จะให้ประสบการณ์แบบโต้ตอบมากขึ้นสำหรับผู้ใช้ฉันกำลังพัฒนา API ที่ใช้บริการและไม่สามารถตั้งสมมติฐานเกี่ยวกับกฎที่ผู้โทรมีหรือยังไม่ได้ใช้ เป็นเพราะเช็คจำนวนมากเหล่านี้สามารถมอบให้กับ UI ได้อย่างง่ายดายซึ่งฉันเลือกใช้โครงการ API เป็นพื้นฐานสำหรับการโพสต์ ฉันกำลังมองหาแนวทางปฏิบัติที่ดีที่สุดแทนที่จะเป็นหนังสือเรียนที่ง่ายและรวดเร็ว
SonOfPirate

@SonOfPirate, INMO, UI ต้องทำการตรวจสอบเพราะมันเร็วกว่าและมีข้อมูลมากกว่าบริการ (ในบางกรณี) ตอนนี้บริการไม่ควรส่งข้อมูลนอกขอบเขตโดยไม่ต้องทำการตรวจสอบของตัวเองเพราะนี่เป็นส่วนหนึ่งของความรับผิดชอบตราบใดที่คุณต้องการให้บริการไม่เชื่อถือลูกค้า ดังนั้นฉันขอแนะนำให้ทำการตรวจสอบที่ไม่ใช่ฐานข้อมูลในบริการ (อีกครั้ง) ก่อนที่จะส่งข้อมูลไปยังฐานข้อมูลสำหรับการประมวลผลเพิ่มเติม
NoChance
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.