ความแตกต่างระหว่างพื้นที่เก็บข้อมูลและชั้นบริการ?


191

ในรูปแบบการออกแบบ OOP ความแตกต่างระหว่างรูปแบบพื้นที่เก็บข้อมูลและชั้นบริการคืออะไร

ฉันกำลังทำงานกับแอพ ASP.NET MVC 3 และพยายามที่จะเข้าใจรูปแบบการออกแบบเหล่านี้ แต่สมองของฉันไม่เข้าใจเลย ... ยัง !!

คำตอบ:


330

Repository Layer ให้คุณเพิ่มระดับของ abstraction ผ่าน data access แทนที่จะเขียน

var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();

ในการรับไอเท็มเดี่ยวจากฐานข้อมูลคุณใช้อินเตอร์เฟสที่เก็บ

public interface IRepository<T>
{
    IQueryable<T> List();
    bool Create(T item);
    bool Delete(int id);
    T Get(int id);
    bool SaveChanges();
}

Get(id)และโทร เลเยอร์ที่เก็บข้อมูลจะเปิดเผยการดำเนินการCRUDขั้นพื้นฐาน

Service layer แสดงตรรกะทางธุรกิจซึ่งใช้ที่เก็บ ตัวอย่างบริการอาจมีลักษณะดังนี้:

public interface IUserService
{
    User GetByUserName(string userName);
    string GetUserNameByEmail(string email);
    bool EditBasicUserData(User user);
    User GetUserByID(int id);
    bool DeleteUser(int id);
    IQueryable<User> ListUsers();
    bool ChangePassword(string userName, string newPassword);
    bool SendPasswordReminder(string userName);
    bool RegisterNewUser(RegisterNewUserModel model);
}

ขณะที่List()เมธอดของที่เก็บส่งคืนผู้ใช้ทั้งหมด แต่ListUsers()IUserService สามารถส่งคืนได้เฉพาะผู้ใช้ที่มีสิทธิ์เข้าถึง

ใน ASP.NET MVC + EF + SQL SERVER ฉันมีกระบวนการสื่อสารนี้:

Views <- Controllers -> Service layer -> Repository layer -> EF -> SQL Server

Service layer -> Repository layer -> EFส่วนนี้ใช้กับรุ่นต่างๆ

Views <- Controllers -> เลเยอร์บริการส่วนนี้ทำงานกับรุ่นที่มีมุมมอง

แก้ไข:

ตัวอย่างของการไหลสำหรับ / คำสั่งซื้อ / ByClient / 5 (เราต้องการดูคำสั่งซื้อสำหรับลูกค้าที่เฉพาะเจาะจง):

public class OrderController
{
    private IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService; // injected by IOC container
    }

    public ActionResult ByClient(int id)
    {
        var model = _orderService.GetByClient(id);
        return View(model); 
    }
}

นี่คืออินเทอร์เฟซสำหรับบริการสั่งซื้อ:

public interface IOrderService
{
    OrdersByClientViewModel GetByClient(int id);
}

อินเตอร์เฟสนี้ส่งคืนโมเดลมุมมอง:

public class OrdersByClientViewModel
{
     CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
     IEnumerable<OrderViewModel> Orders { get; set; }
}

นี่คือการใช้อินเตอร์เฟส มันใช้คลาสโมเดลและที่เก็บเพื่อสร้างมุมมองโมเดล:

public class OrderService : IOrderService
{
     IRepository<Client> _clientRepository;
     public OrderService(IRepository<Client> clientRepository)
     {
         _clientRepository = clientRepository; //injected
     }

     public OrdersByClientViewModel GetByClient(int id)
     {
         return _clientRepository.Get(id).Select(c => 
             new OrdersByClientViewModel 
             {
                 Cient = new ClientViewModel { ...init with values from c...}
                 Orders = c.Orders.Select(o => new OrderViewModel { ...init with values from o...}     
             }
         );
     }
}

2
@ Sam Striano: อย่างที่คุณเห็นด้านบน IRepository ของฉันส่งคืน IQueryable สิ่งนี้อนุญาตให้เพิ่มเพิ่มเติมโดยที่เงื่อนไขและการดำเนินการที่เลื่อนออกไปในเลเยอร์บริการไม่ใช่ในภายหลัง ใช่ฉันใช้แอสเซมบลีหนึ่ง แต่คลาสเหล่านี้ทั้งหมดจะวางใน namespaces ที่แตกต่างกัน ไม่มีเหตุผลที่จะสร้างชุดประกอบจำนวนมากในโครงการขนาดเล็ก การแยกชื่อและโฟลเดอร์ทำงานได้ดี
LukLed

81
ทำไมถึงต้องส่งคืนโมเดลการรับชมในบริการ บริการไม่ควรเลียนแบบหากคุณมีลูกค้าหลายราย (มือถือ / เว็บ) หากเป็นเช่นนั้นโมเดลการดูอาจแตกต่างจากแพลตฟอร์มที่แตกต่างกัน
Ryan

12
เห็นด้วยกับ @Ryan ชั้นบริการควรส่งคืนเอนทิตีวัตถุหรือคอลเลกชันของวัตถุเอนทิตี (ไม่ใช่ IQueryable) จากนั้นในเอนทิตี ui ไปที่ SomeViewModel โดย Automapper เป็นต้น
Eldar

5
@Duffp: คุณไม่จำเป็นต้องสร้างที่เก็บข้อมูลสำหรับทุกเอนทิตี คุณสามารถใช้การติดตั้งทั่วไปและผูกIRepository<>ไว้GenericRepository<>ในไลบรารี IOC ของคุณ คำตอบนี้เก่ามาก UnitOfWorkผมคิดว่าทางออกที่ดีที่สุดคือการรวมที่เก็บทั้งหมดในระดับหนึ่งที่เรียกว่า SaveChangesมันควรจะมีพื้นที่เก็บข้อมูลของทุกชนิดและวิธีการหนึ่งที่เรียกว่า ที่เก็บทั้งหมดควรแบ่งปันบริบทของ EF หนึ่งบริบท
LukLed

2
แทนที่จะกลับมาดูโมเดลจากเลเยอร์บริการคุณควรคืน DTO และแปลงมันเป็น viewModels โดยใช้ automapper .. บางครั้งมันเหมือนกันเมื่อพวกเขาไม่ใช่คุณจะขอบคุณคุณได้ใช้ YGTNI "คุณจะ Need It "
hanzolo

41

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

วัตถุประสงค์ของเลเยอร์บริการในอีกทางหนึ่งคือการห่อหุ้มตรรกะทางธุรกิจไว้ในที่เดียวเพื่อส่งเสริมการใช้ซ้ำรหัสและการแยกข้อกังวล สิ่งนี้มักจะมีความหมายสำหรับฉันในทางปฏิบัติเมื่อสร้างไซต์ MVC ของ Asp.net คือฉันมีโครงสร้างนี้

[คอนโทรลเลอร์] โทร [บริการ (s)] ที่โทร [ที่เก็บ (IES)]

หลักการหนึ่งที่ฉันพบว่ามีประโยชน์คือการทำให้ตรรกะมีค่าน้อยที่สุดในคอนโทรลเลอร์และที่เก็บ

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

ในที่เก็บเป็นเพราะฉันต้องการที่จะสามารถแทนที่ที่เก็บข้อมูลของฉัน (หรือ ORM) เมื่อสิ่งที่ดีกว่ามาพร้อม และถ้าฉันมีตรรกะในที่เก็บฉันต้องเขียนตรรกะนี้ใหม่เมื่อฉันเปลี่ยนที่เก็บ หากที่เก็บของฉันส่งคืน IQueryable เท่านั้นและบริการทำการกรองในอีกทางหนึ่งฉันจะต้องเปลี่ยนการแมปเท่านั้น

ตัวอย่างเช่นเมื่อเร็ว ๆ นี้ฉันได้แทนที่ที่เก็บ Linq-To-Sql หลายแห่งของฉันด้วย EF4 และที่ที่ฉันยังคงยึดมั่นกับหลักการนี้สามารถเปลี่ยนได้ภายในไม่กี่นาที ที่ฉันมีเหตุผลบางอย่างมันเป็นเรื่องของชั่วโมงแทน


ฉันเห็นด้วยกับคุณมิคาเอล ในความเป็นจริงฉันได้ใช้สถานการณ์เดียวกันในบล็อกเทคโนโลยีของฉันfreecodebase.comและฉันใช้รหัสวิธีแรกในการดำเนินการนี้ สามารถดาวน์โหลดซอร์สโค้ดได้ที่นี่เช่นกัน
Toffee

ฉันค้นคว้าเรื่องทั่วไปของการใช้รูปแบบที่เก็บในแอป MVC ที่มีอยู่ มันเป็นเฟรมเวิร์ก bespoke กับ Active Record-like ORM และอนุสัญญา Rails / Laravel อื่น ๆ และมีปัญหาสถาปัตยกรรมบางอย่างสำหรับงานที่ฉันกำลังทำอยู่ สิ่งหนึ่งที่ฉันเจอคือที่เก็บ "ไม่ควรส่งคืน ViewModels, DTO หรือวัตถุของวัตถุ" แต่ควรกลับวัตถุที่เก็บ ฉันคิดว่าบริการโต้ตอบกับวัตถุที่เก็บข้อมูลด้วยวิธีการที่ชอบonBeforeBuildBrowseQueryและสามารถใช้ตัวสร้างแบบสอบถามเพื่อแก้ไขแบบสอบถาม
calligraphic-io

@ Coffee ลิงค์ของคุณเสียคุณช่วยกรุณาอัปเดตฉันต้องการซอร์สโค้ดสำหรับการติดตั้งนี้
Hamza Khanzada

24

คำตอบที่ยอมรับ (และเพิ่มขึ้นหลายร้อยครั้ง) มีข้อบกพร่องที่สำคัญ ฉันต้องการที่จะชี้ให้เห็นในความคิดเห็น แต่มันก็จะถูกฝังอยู่ที่นั่นในบางสิ่ง 30 ความเห็นดังนั้นชี้ไปที่นี่

ฉันเข้าซื้อแอปพลิเคชันระดับองค์กรซึ่งสร้างขึ้นด้วยวิธีนี้และปฏิกิริยาเริ่มต้นของฉันคือWTH ? ViewModels ในชั้นบริการหรือไม่ ฉันไม่ต้องการเปลี่ยนการประชุมเพราะมีการพัฒนามาหลายปีฉันจึงกลับไปดู ViewModels ต่อไป เด็กชายมันกลายเป็นฝันร้ายเมื่อเราเริ่มใช้ WPF พวกเรา (ทีมงาน devs) มักจะพูดว่า: ViewModel ตัวไหน? ของจริง (ของที่เราเขียนสำหรับ WPF) หรือบริการหนึ่ง พวกเขาเขียนขึ้นสำหรับเว็บแอปพลิเคชันและยังมีการตั้งค่าสถานะIsReadOnlyเพื่อปิดการใช้งานการแก้ไขใน UI เมเจอร์ข้อบกพร่องที่สำคัญและทั้งหมดเพราะหนึ่งคำ: ViewModel !!

ก่อนที่คุณจะทำผิดพลาดต่อไปนี้เป็นเหตุผลเพิ่มเติมบางส่วนนอกเหนือจากเรื่องราวของฉันด้านบน:

การคืน ViewModel จากเลเยอร์บริการเป็นสิ่งที่ไม่ใหญ่มาก นั่นเหมือนการพูดว่า:

  1. หากคุณต้องการใช้บริการเหล่านี้คุณควรใช้ MVVM และนี่คือ ViewModel ที่คุณต้องใช้ อุ๊ย!

  2. บริการต่าง ๆ กำลังตั้งสมมติฐานว่าพวกเขาจะแสดงใน UI ที่ใดที่หนึ่ง จะเกิดอะไรขึ้นหากแอปพลิเคชันที่ไม่ใช่ UI ใช้เช่นบริการเว็บหรือบริการ windows

  3. นั่นไม่ใช่แม้แต่ ViewModel จริง ViewModel จริงมีความสามารถในการสังเกตการณ์คำสั่ง ฯลฯ นั่นเป็นเพียง POCO ที่มีชื่อไม่ดี (ดูเรื่องราวของฉันด้านบนเพราะเหตุใดชื่อจึงสำคัญ)

  4. แอปพลิเคชั่นที่ใช้งานได้ดีกว่าจะเป็นเลเยอร์การนำเสนอ (ใช้ ViewModels โดยเลเยอร์นี้) และเข้าใจ C # ได้ดียิ่งขึ้น อุ๊ย!

โปรดอย่าทำอย่างนั้น!


3
ฉันต้องแสดงความคิดเห็นในเรื่องนี้แม้ว่าฉันจะรู้ว่ามันไม่ได้เพิ่มการอภิปราย: "นั่นเป็นเพียง POCO ที่มีชื่อไม่ดี" << - มันดูดีบนเสื้อยืด! :) :)
Mephisztoe

8

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


ดังนั้นในแอป ASP.NET MVC ที่ใช้ EF4 อาจจะเป็นเช่นนี้: SQL Server -> EF4 -> Repository -> Service Layer -> Model -> Controller - และในทางกลับกัน?
Sam

1
ใช่ที่เก็บของคุณสามารถใช้รับเอนทิตีที่มีน้ำหนักเบาจาก EF4 และเลเยอร์บริการของคุณสามารถใช้เพื่อส่งกลับไปยังตัวจัดการโมเดลพิเศษ (โมเดลในสถานการณ์ของคุณ) คอนโทรลเลอร์จะเรียกใช้ตัวจัดการโมเดลเฉพาะของคุณเพื่อทำสิ่งนี้ ... ลองดูที่บล็อกของฉันสำหรับ Mvc 2/3 ฉันมีไดอะแกรม
CarneyCode

เพื่อความชัดเจน: EF4 ในสถานการณ์ของคุณคือที่ Model อยู่ในไดอะแกรมของฉันและ Model ในสถานการณ์ของคุณเป็นผู้จัดการโมเดลเฉพาะในไดอะแกรมของฉัน
CarneyCode

5

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.