Unit-of-Work & Repository Framework aplicada a DDD
A URF, Unit-of-Work & Repository Framework, é uma framework que tem por objectivo juntar as Patterns Repository e Unit of Work sobre tecnologia O/RM ao Domain-Driven Design. Documentação desta framework aqui.
O exercício seguinte consiste em aplicar a URF Framework à solução desenhada aqui, com dependency injection.
Criação da solução
Criar a nova solução UnitOfWorkRepository.Framework com:- Pasta Application
- Pasta Domain
- Pasta Infrastructure
Criação dos projectos Domain-Driven
À Infrastructure adiciona-se o projecto do tipo biblioteca de nome EntityFramework com os repositórios de acesso à Entity Framework.Ao Domain adiciona-se o projecto do tipo biblioteca com o Core.
À Application adiciona-se o projecto do tipo biblioteca com o nome Application.
Mais detalhes aqui.
Geração de POCO e DbContext
Na Consola de Gestão de Pacotes NuGet procede-se à instalação da Entity Framework, tendo o projecto EntityFramework como padrão.PM> install-package EntityFramework
Uma vez instalada a última versão da Entity Framework, procede-se ao reverse engineering para a geração do DbContext, classes POCO e Configuração POCO a partir da BD SQL Server Pluto_Queries. É usado para o efeito o módulo EntityFramework Reverse POCO Generator, disponível aqui, que integra com o Visual Studio 2017. Artigo dedicado, aqui, pela Microsoft MVP Julie Nerman.
Connection string PlutoContext usada aqui:
<connectionStrings>
<add name="PlutoContext" connectionString="data source=(localdb)\MSSQLLocalDB;initial catalog=Pluto_Queries;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
Mover as classes POCO Author e Course, geradas automaticamente pelo EF.Reverse.POCO.Generator para a pasta Model do Core e a bilbioteca EntityFramework tem de passar a referenciá-lo.
using UnitOfWorkRepository.Framework.Core.Model;
Compilar a solução para verificação.
Integração com a URF Framework
Usar a gestão de pacotes NuGet através da UI da ferramenta no VS para instalar o pacote Urf.Repository.Pattern.EF6 nos projectos Application, Core e EntityFramework.Integração do DbContext
Para integrar o DbContext com a URF Framework, o DbContext tem de herdar do DataContext:namespace UnitOfWorkRepository.Framework.EntityFramework
{
using System.Linq;
using UnitOfWorkRepository.Framework.Core.Model;
using Repository.Pattern.Ef6;
[System.CodeDom.Compiler.GeneratedCode("EF.Reverse.POCO.Generator", "2.32.0.0")]
public partial class PlutoContext : DataContext, IPlutoContext
{
public System.Data.Entity.DbSet<Author> Authors { get; set; } // Authors
public System.Data.Entity.DbSet<Course> Courses { get; set; } // Courses
static PlutoContext()
{
System.Data.Entity.Database.SetInitializer<PlutoContext>(null);
}
public PlutoContext()
: base("Name=PlutoContext")
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
[...]
}
}
Integração do Model
Para integrar as entidades de domínio com esta framework de persistência, estas classes têm de herdar da classe base Entity.cls:namespace UnitOfWorkRepository.Framework.Core.Model
{
using Repository.Pattern.Ef6;
// Authors
[System.CodeDom.Compiler.GeneratedCode("EF.Reverse.POCO.Generator", "2.32.0.0")]
public partial class Author: Entity
{
[...]
}
}
Compilar a solução para verificação.
Integração das Interfaces
À biblioteca Core, adiciona-se a pasta Interfaces. Esta pasta comporta as interfaces intrínsecas aos repositórios e estendem a interface de repositório da URF Framework. Exemplo, IAuthorRepository:using UnitOfWorkRepository.Framework.Core.Model;
using Repository.Pattern.Repositories;
namespace UnitOfWorkRepository.Framework.Core.Interfaces
{
public interface IAuthorRepository: IRepositoryAsync<Author>
{
Author GetAuthorWithCourses(int id);
}
}
Compilar a solução para verificação.
Integração dos Repositórios
Depois de adicionada a pasta Repositories à biblioteca EntityFramework, juntam-se as classes que implementam as interfaces definidas no Domain e que herdam do repositório base da URF Framework. Exemplo, AuthorRepository:using Repository.Pattern.DataContext;
using Repository.Pattern.Ef6;
using Repository.Pattern.UnitOfWork;
using System.Linq;
using UnitOfWorkRepository.Framework.Core.Interfaces;
using UnitOfWorkRepository.Framework.Core.Model;
namespace UnitOfWorkRepository.Framework.EntityFramework.Repositories
{
public class AuthorRepository: Repository <Author>, IAuthorRepository
{
public AuthorRepository(IDataContextAsync dataContext, IUnitOfWorkAsync unitOfWork)
: base(dataContext, unitOfWork){}
public Author GetAuthorWithCourses(int id)
{
var authorWithCourses = base.Query().Include(a => a.Course).Select().AsQueryable();
return authorWithCourses.FirstOrDefault();
}
}
}
Introdução da lógica de negócio
Usar a gestão de pacotes NuGet através da UI da ferramenta no VS para instalar o pacote Urf.Service.Pattern nos projectos Application e Core.A biblioteca Application vai ter os serviços que implementam a lógica de negócio, enquanto a pasta Interfaces da biblioteca Core vai ter as interfaces intrínsecas a estes serviços. Exemplo, IAuthorService e AuthorService:
using System.Collections.Generic;
using UnitOfWorkRepository.Framework.Core.Model;
using Service.Pattern;
namespace UnitOfWorkRepository.Framework.Core.Interfaces
{
public interface IAuthorService: IService<Author>
{
IEnumerable<Author> GetAllAuthors();
Author GetAuthor(int Id);
Author GetAuthorWithCourses(int id);
}
}
using System.Collections.Generic;
using UnitOfWorkRepository.Framework.Core.Model;
using UnitOfWorkRepository.Framework.Core.Interfaces;
using Service.Pattern;
using Repository.Pattern.Repositories;
namespace UnitOfWorkRepository.Framework.Application
{
public class AuthorService : Service<Author>, IAuthorService
{
private IAuthorRepository _repository;
public AuthorService(IRepositoryAsync<Author> repository)
:base(repository)
{
_repository = repository as IAuthorRepository;
}
public IEnumerable<Author> GetAllAuthors()
{
return base.Query().Select();
}
public Author GetAuthor(int Id)
{
return base.Find(Id);
}
public Author GetAuthorWithCourses(int id)
{
return _repository.GetAuthorWithCourses(id);
}
public override void Insert(Author author)
{
// TODO: lógica de negócio
base.Insert(author);
}
public override void Delete(Author author)
{
// TODO: lógica de negócio
base.Delete(author);
}
public override void Update(Author author)
{
// TODO: lógica de negócio
base.Update(author);
}
}
}
Consumo por framework de apresentação
É usado para exemplo o controller AuthorsController.cs da framework .NET MVC 5.I. Criar o projecto do tipo ASP.NET MVC Web Application no Visual Studio 2017 com a template de fábrica.
II. Usar a gestão de pacotes NuGet através da UI da ferramenta no VS para instalar neste projecto o pacote mais recente do Unity.Mvc (unity bootstrapper para asp.net mvc), pela Microsoft.
III. Adicionar o seguinte código ao App_Start\UnityConfig.cs
using UnitOfWorkRepository.Framework.Application;
using Repository.Pattern.Repositories;
using UnitOfWorkRepository.Framework.Core.Model;
using UnitOfWorkRepository.Framework.Core.Interfaces;
using Repository.Pattern.UnitOfWork;
using Repository.Pattern.Ef6;
using Repository.Pattern.DataContext;
using UnitOfWorkRepository.Framework.EntityFramework;
using UnitOfWorkRepository.Framework.EntityFramework.Repositories;
using Service.Pattern;
container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new PerRequestLifetimeManager());
container.RegisterType<IDataContext, PlutoContext>(new PerRequestLifetimeManager());
container.RegisterType<IDataContextAsync, PlutoContext>(new PerRequestLifetimeManager());
container.RegisterType<IRepositoryAsync<Author>, AuthorRepository>(new PerRequestLifetimeManager());
container.RegisterType<IService<Author>, Service<Author >> (new PerRequestLifetimeManager());
container.RegisterType<IAuthorService, AuthorService>(new PerRequestLifetimeManager());
IV. Descomentar a seguinte linha de código do método Start da App_Start\UnityMvcActivator.cs
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
V. Gerar o AuthorsController com scaffolding com modos de exibição usando a Entity Framework
VI.Substituir o código do AuthorsController.cs por
using Repository.Pattern.Infrastructure;
using Repository.Pattern.UnitOfWork;
using System.Web.Mvc;
using UnitOfWorkRepository.Framework.Core.Interfaces;
using UnitOfWorkRepository.Framework.Core.Model;
namespace UnitOfWorkRepository.Framework.WebApplication.MVC.Controllers
{
public class AuthorsController : Controller
{
private IAuthorService _authorService;
private IUnitOfWork _unitOfWork;
public AuthorsController(IAuthorService authorService, IUnitOfWork unitOfWork)
{
_authorService = authorService;
_unitOfWork = unitOfWork;
}
// GET: Authors
public ActionResult Index()
{
return View(_authorService.GetAllAuthors());
}
// GET: Authors/Details/5
public ActionResult Details(int id)
{
Author author = _authorService.GetAuthor(id);
if (author == null)
{
return HttpNotFound();
}
return View(author);
}
// GET: Authors/Create
public ActionResult Create()
{
return View();
}
// POST: Authors/Create
// Para se proteger de mais ataques, ative as propriedades específicas a que você quer se conectar. Para
// obter mais detalhes, consulte https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name")] Author author)
{
if (ModelState.IsValid)
{
_authorService.Insert(author);
_unitOfWork.SaveChanges();
return RedirectToAction("Index");
}
return View(author);
}
// GET: Authors/Edit/5
public ActionResult Edit(int id)
{
Author author = _authorService.GetAuthor(id);
if (author == null)
{
return HttpNotFound();
}
return View(author);
}
// POST: Authors/Edit/5
// Para se proteger de mais ataques, ative as propriedades específicas a que você quer se conectar. Para
// obter mais detalhes, consulte https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,Name")] Author author)
{
if (ModelState.IsValid)
{
author.ObjectState = ObjectState.Modified;
_authorService.Update(author);
_unitOfWork.SaveChanges();
return RedirectToAction("Index");
}
return View(author);
}
// GET: Authors/Delete/5
public ActionResult Delete(int id)
{
Author author = _authorService.GetAuthor(id);
if (author == null)
{
return HttpNotFound();
}
return View(author);
}
// POST: Authors/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Author author = _authorService.GetAuthor(id);
_authorService.Delete(author);
_unitOfWork.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_unitOfWork.Dispose();
}
base.Dispose(disposing);
}
}
}
Depurar no navegador pré-definido.
<url base>/Authors
Relacionado: Unit of Work in Repository Pattern, Arquitectura por camadas em aplicações ASP.NET Core
(artigo editado)
Referências: Introduction to EntityFramework Reverse POCO Generator for Visual Studio, EntityFramework Reverse POCO Generator, URF (Unit of Work & Repository Framework) in ASP.NET MVC 5 with Entity Framework 6 & Unity 3 - v2
Licença CC BY-SA 4.0
Silvia Pinhão Lopes, 23.10.17
Sem comentários: