ASP.NET MVC에서 데이터 액세스 분리


35

MVC의 첫 번째 실제 균열로 업계 표준 및 모범 사례를 따르고 싶습니다. 이 경우 C #을 사용하는 ASP.NET MVC입니다.

코드 우선 개체 (데이터베이스가 이미 존재 함)와 함께 모델에 Entity Framework 4.1을 사용하므로 데이터베이스에서 데이터를 검색하기위한 DBContext 개체가 있습니다.

asp.net 웹 사이트에서 살펴본 데모에서 컨트롤러에는 데이터 액세스 코드가 있습니다. 이것은 특히 DRY (반복하지 마십시오) 관행을 따를 때 나에게 옳지 않은 것처럼 보입니다.

예를 들어, 공공 도서관에서 사용할 웹 애플리케이션을 작성 중이고 카탈로그에서 책을 작성, 업데이트 및 삭제하기위한 컨트롤러가 있다고 가정합니다.

일부 작업은 ISBN을 사용하여 "Book"개체를 반환해야합니다 (100 % 유효한 코드는 아님).

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }

    public ActionResult Edit(String ISBNtoGet)
    {
        Book currentBook = _db.Books.Single(b => b.ISBN == ISBNtoGet);
        return View(currentBook);
    }
}

대신 실제로 하나의 Book을 반환하는 db 컨텍스트 객체에 메소드가 있어야 합니까? 그것은 나에게 더 나은 분리 인 것처럼 보이고 DRY를 촉진하는 데 도움이됩니다. 웹 응용 프로그램의 다른 곳에서 ISBN으로 Book 객체를 가져와야 할 수도 있기 때문입니다.

public partial class LibraryDBContext: DBContext
{
    public Book GetBookByISBN(String ISBNtoGet)
    {
        return Books.Single(b => b.ISBN == ISBNtoGet);
    }
}

public class BookController : Controller
{
    LibraryDBContext _db = new LibraryDBContext();

    public ActionResult Details(String ISBNtoGet)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }

    public ActionResult Edit(ByVal ISBNtoGet as String)
    {
        return View(_db.GetBookByISBN(ISBNtoGet));
    }
}

내 응용 프로그램을 코딩 할 때 따라야 할 유효한 규칙입니까?

또는 더 주관적인 질문은 "이것이 올바른 방법입니까?"일 것입니다.

답변:


55

일반적으로 컨트롤러가 몇 가지 작업 만 수행하려고합니다.

  1. 수신 요청 처리
  2. 처리를 일부 비즈니스 오브젝트에 위임
  3. 비즈니스 처리 결과를 렌더링을위한 적절한보기로 전달

이 안 어떤 컨트롤러에서 데이터 액세스 또는 복잡한 비즈니스 로직.

[가장 간단한 앱에서는 컨트롤러에서 기본 데이터 CRUD 작업을 수행 할 수 있지만 간단한 Get 및 Update 호출을 추가하기 시작하면 처리를 별도의 클래스로 분리하려고합니다. ]

컨트롤러는 일반적으로 실제 처리 작업을 수행하기 위해 '서비스'에 의존합니다. 서비스 클래스에서는 데이터 소스 (귀하의 경우 DbContext)와 직접 작업 할 있지만 데이터 액세스 외에도 많은 비즈니스 규칙을 작성하는 경우 비즈니스를 분리하고 싶을 수 있습니다. 데이터 액세스의 논리.

이 시점에서 데이터 액세스 외에는 아무것도하지 않는 클래스가있을 것입니다. 때때로 이것을 리포지토리라고 부르지 만 실제로는 그 이름이 중요하지 않습니다. 요점은 데이터베이스로 데이터를 가져오고 나가는 데 필요한 모든 코드가 한 곳에 있다는 것입니다.

내가 작업 한 모든 MVC 프로젝트에 대해 항상 다음과 같은 구조로 끝났습니다.

제어 장치

public class BookController : Controller
{
    ILibraryService _libraryService;

    public BookController(ILibraryService libraryService)
    {
        _libraryService = libraryService;
    }

    public ActionResult Details(String isbn)
    {
        Book currentBook = _libraryService.RetrieveBookByISBN(isbn);
        return View(ConvertToBookViewModel(currentBook));
    }

    public ActionResult DoSomethingComplexWithBook(ComplexBookActionRequest request)
    {
        var responseViewModel = _libraryService.ProcessTheComplexStuff(request);
        return View(responseViewModel);
    }
}

비즈니스 서비스

public class LibraryService : ILibraryService
{
     IBookRepository _bookRepository;
     ICustomerRepository _customerRepository;

     public LibraryService(IBookRepository bookRepository, 
                           ICustomerRepository _customerRepository )
     {
          _bookRepository = bookRepository;
          _customerRepository = customerRepository;
     }

     public Book RetrieveBookByISBN(string isbn)
     {
          return _bookRepository.GetBookByISBN(isbn);
     }

     public ComplexBookActionResult ProcessTheComplexStuff(ComplexBookActionRequest request)
     {
          // Possibly some business logic here

          Book book = _bookRepository.GetBookByISBN(request.Isbn);
          Customer customer = _customerRepository.GetCustomerById(request.CustomerId);

          // Probably more business logic here

          _libraryRepository.Save(book);

          return complexBusinessActionResult;

     } 
}

저장소

public class BookRepository : IBookRepository
{
     LibraryDBContext _db = new LibraryDBContext();

     public Book GetBookByIsbn(string isbn)
     {
         return _db.Books.Single(b => b.ISBN == isbn);
     }

     // And the rest of the data access
}

+1 저장소 추상화가 어떤 가치를 제공하는지에 대해서는 의문이 있지만 전반적으로 훌륭한 조언.
MattDavey

3
@MattDavey 예, 처음 (또는 가장 간단한 앱)에는 리포지토리 계층의 필요성을 파악하기가 어렵지만 비즈니스 로직이 어느 정도 복잡 해지 자마자 이는 쉽지 않습니다. 데이터 액세스를 분리하십시오. 그러나 간단한 방법으로 전달하는 것은 쉽지 않습니다.
Eric King

1
@Billy IoC 커널은 MVC 프로젝트에 있을 필요 는 없습니다 . MVC 프로젝트는 의존하지만 리포지토리 프로젝트에 의존하는 자체 프로젝트로 가질 수 있습니다. 필요를 느끼지 않기 때문에 일반적으로 그렇게하지 않습니다. 그럼에도 불구하고 MVC 프로젝트가 리포지토리 클래스를 호출하지 않으려면하지 마십시오. 나는 햄스트링을 좋아하는 팬이 아니기 때문에 내가 관여하지 않을 프로그래밍 관행의 가능성으로부터 자신을 보호 할 수있다.
Eric King

2
우리는 정확히이 패턴을 사용합니다 : Controller-Service-Repository. 서비스 / 리포지토리 계층이 매개 변수 개체 (예 : GetBooksParameters)를 가져 와서 ILibraryService에서 확장 메서드를 사용하여 매개 변수 조정을 수행하는 것이 매우 유용하다고 덧붙이고 싶습니다. 그런 식으로 ILibraryService는 객체를 취하는 간단한 진입 점을 가지고 있으며, 확장 메소드는 매번 인터페이스와 클래스를 다시 쓰지 않고도 가능한 한 매개 변수가 될 수 있습니다 (예 : GetBooksByISBN / Customer / Date / GetBooksParameters 객체를 형성하고 서비스). 콤보는 훌륭했습니다.
BlackjacketMack

1
@IsaacKleinman 나는 어떤 위대한 작품이 그것을 쓴 것을 기억하지 못하지만 (Bob Martin?) 근본적인 질문입니다 .Oven.Bake (피자) 또는 Pizza.Bake (오븐)을 원하십니까? 대답은 '의존적'입니다. 일반적으로 우리는 하나 이상의 물체 (또는 피자)를 조작하는 외부 서비스 (또는 작업 단위)를 원합니다. 그러나 그 개별 객체는 구운 오븐 유형에 반응 할 수있는 능력이 없다고 말할 수 있습니다. 나는 OrderRepository.Save (order)를 Order.Save ()보다 선호합니다. 그러나 주문이 이상적인 형태임을 알 수 있기 때문에 Order.Validate ()를 좋아합니다. 상황에 맞는 개인.
BlackjacketMack

2

구현을 바꿀 수 있도록 데이터 공급자를 일반 데이터 서비스 인터페이스로 주입하고 있지만 이것이 내가하고있는 방식입니다.

내가 아는 한, 컨트롤러는 데이터를 얻고, 조치를 수행하고, 데이터를보기에 전달하는 곳입니다.


예, 데이터 제공 업체에 "서비스 인터페이스"를 사용하는 방법에 대해 읽었습니다. 단위 테스트에 도움이되기 때문입니다.
scott.korin 2019
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.