Projetar a camada de persistência da infraestruturaDesign the infrastructure persistence layer

Os componentes de persistência de dados fornecem acesso aos dados hospedados dentro dos limites de um microsserviço (ou seja, em um banco de dados de microsserviço).Data persistence components provide access to the data hosted within the boundaries of a microservice (that is, a microservice’s database). Eles contêm a implementação real dos componentes, como repositórios e classes Unidade de Trabalho, como objetos DbContext personalizados do EF (Entity Framework).They contain the actual implementation of components such as repositories and Unit of Work classes, like custom Entity Framework (EF) DbContext objects. O DbContext do EF implementa os padrões de Repositório e de Unidade de Trabalho.EF DbContext implements both, the Repository and the Unit of Work patterns.

O padrão de repositórioThe Repository pattern

Repositórios são classes ou componentes que encapsulam a lógica necessária para acessar fontes de dados.Repositories are classes or components that encapsulate the logic required to access data sources. Eles centralizam a funcionalidade comum de acesso a dados, melhorando a sustentabilidade e desacoplando a infraestrutura ou a tecnologia usada para acessar os bancos de dados da camada do modelo de domínio.They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. Se você usar um ORM (Mapeador Objeto-Relacional) como o Entity Framework, o código que precisa ser implementado será simplificado, graças à LINQ e à tipagem forte.If you use an Object-Relational Mapper (ORM) like Entity Framework, the code that must be implemented is simplified, thanks to LINQ and strong typing. Isso permite que você se concentre na lógica de persistência de dados e não nos detalhes técnicos do acesso a dados.This lets you focus on the data persistence logic rather than on data access plumbing.

O padrão de repositório é uma maneira bem documentada de trabalhar com uma fonte de dados.The Repository pattern is a well-documented way of working with a data source. No livro Padrões de Arquitetura de Aplicações Corporativas, Martin Fowler descreve um repositório da seguinte maneira:In the book Patterns of Enterprise Application Architecture, Martin Fowler describes a repository as follows:

Um repositório executa as tarefas de um intermediário entre as camadas de modelo de domínio e o mapeamento de dados, funcionando de maneira semelhante a um conjunto de objetos de domínio na memória.A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory. Os objetos de clientes criam consultas de forma declarativa e enviam-nas para os repositórios buscando respostas.Client objects declaratively build queries and send them to the repositories for answers. Conceitualmente, um repositório encapsula um conjunto de objetos armazenados no banco de dados e as operações que podem ser executadas neles, fornecendo uma maneira que é mais próxima da camada de persistência.Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer. Os repositórios também oferecem a capacidade de separação, de forma clara e em uma única direção, a dependência entre o domínio de trabalho e a alocação de dados ou o mapeamento.Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.

Definir um repositório por agregaçãoDefine one repository per aggregate

Para cada agregação ou raiz de agregação, você deve criar uma classe de repositório.For each aggregate or aggregate root, you should create one repository class. Em um microsserviço baseado nos padrões de DDD (Design Orientado por Domínio), o único canal que você deve usar para atualizar o banco de dados são os repositórios.In a microservice based on Domain-Driven Design (DDD) patterns, the only channel you should use to update the database should be the repositories. O motivo é que eles têm uma relação um-para-um com a raiz de agregação, que controla as invariáveis e a consistência transacional da agregação.This is because they have a one-to-one relationship with the aggregate root, which controls the aggregate’s invariants and transactional consistency. É possível consultar o banco de dados por outros canais (como ao seguir uma abordagem de CQRS), porque as consultas não alteram o estado do banco de dados.It's okay to query the database through other channels (as you can do following a CQRS approach), because queries don't change the state of the database. No entanto, a área transacional (ou seja, as atualizações) sempre precisa ser controlada pelos repositórios e pelas raízes de agregação.However, the transactional area (that is, the updates) must always be controlled by the repositories and the aggregate roots.

Basicamente, um repositório permite popular na memória dados que são provenientes do banco de dados, em forma de entidades de domínio.Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. Depois que as entidades estão na memória, elas podem ser alteradas e persistidas novamente no banco de dados por meio de transações.Once the entities are in memory, they can be changed and then persisted back to the database through transactions.

Conforme observado anteriormente, se você estiver usando o padrão de arquitetura CQS/CQRS, as consultas inicias serão executadas por consultas à parte, fora do modelo de domínio, executadas por instruções SQL simples usando o Dapper.As noted earlier, if you're using the CQS/CQRS architectural pattern, the initial queries are performed by side queries out of the domain model, performed by simple SQL statements using Dapper. Essa abordagem é muito mais flexível do que os repositórios porque você pode consultar e unir as tabelas necessárias, e essas consultas não são restringidas pelas regras das agregações.This approach is much more flexible than repositories because you can query and join any tables you need, and these queries aren't restricted by rules from the aggregates. Esses dados vão para o aplicativo cliente ou de camada de apresentação.That data goes to the presentation layer or client app.

Se o usuário fizer alterações, os dados a serem atualizados virão da camada de apresentação ou do aplicativo cliente para a camada do aplicativo (como um serviço de API Web).If the user makes changes, the data to be updated comes from the client app or presentation layer to the application layer (such as a Web API service). Ao receber um comando em um manipulador de comandos, use repositórios para obter os dados que deseja atualizar do banco de dados.When you receive a command in a command handler, you use repositories to get the data you want to update from the database. Você os atualizará na memória com os dados transmitidos com os comandos e, em seguida, adicionará ou atualizará esses dados (entidades de domínio) no banco de dados por meio de uma transação.You update it in memory with the data passed with the commands, and you then add or update the data (domain entities) in the database through a transaction.

É importante enfatizar novamente que você deve definir apenas um repositório para cada raiz de agregação, conforme mostrado na Figura 7-17.It's important to emphasize again that you should only define one repository for each aggregate root, as shown in Figure 7-17. Para atingir a meta da raiz de agregação de manter a consistência transacional entre todos os objetos na agregação, você nunca deve criar um repositório para cada tabela no banco de dados.To achieve the goal of the aggregate root to maintain transactional consistency between all the objects within the aggregate, you should never create a repository for each table in the database.

Diagrama mostrando relações de domínio e outra infraestrutura.

Figura 7-17.Figure 7-17. A relação entre repositórios, agregações e tabelas de banco de dadosThe relationship between repositories, aggregates, and database tables

O diagrama acima mostra as relações entre as camadas de infraestrutura e de domínio: o comprador agregado depende do IBuyerRepository e a agregação de ordem depende das interfaces IOrderRepository, essas interfaces são implementadas na camada de infraestrutura pelos repositórios correspondentes que dependem de UnitOfWork, também implementados ali, que acessam as tabelas na camada de dados.The above diagram shows the relationships between Domain and Infrastructure layers: Buyer Aggregate depends on the IBuyerRepository and Order Aggregate depends on the IOrderRepository interfaces, these interfaces are implemented in the Infrastructure layer by the corresponding repositories that depend on UnitOfWork, also implemented there, that accesses the tables in the Data tier.

Impor uma raiz de agregação por repositórioEnforce one aggregate root per repository

É importante implementar o design do repositório de uma forma que ele imponha a regra de que apenas as raízes de agregação devem ter repositórios.It can be valuable to implement your repository design in such a way that it enforces the rule that only aggregate roots should have repositories. Você pode criar um tipo de repositório genérico ou de base que restrinja o tipo de entidades com as quais trabalha para garantir que elas tenham a interface de marcador IAggregateRoot.You can create a generic or base repository type that constrains the type of entities it works with to ensure they have the IAggregateRoot marker interface.

Assim, cada classe de repositório implementada na camada de infraestrutura implementa seu próprio contrato ou interface, conforme é mostrado no código a seguir:Thus, each repository class implemented at the infrastructure layer implements its own contract or interface, as shown in the following code:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

Cada interface de repositório específica implementa a interface IRepository genérica:Each specific repository interface implements the generic IRepository interface:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

No entanto, uma maneira melhor de fazer com que o código imponha a convenção de que cada repositório seja relacionado a uma única agregação é implementar um tipo de repositório genérico.However, a better way to have the code enforce the convention that each repository is related to a single aggregate is to implement a generic repository type. Dessa forma, é obrigatório que você esteja usando um repositório para direcionar a uma agregação específica.That way, it's explicit that you're using a repository to target a specific aggregate. Isso pode ser feito facilmente com a implementação de uma interface base IRepository genérica, como no código a seguir:That can be easily done by implementing a generic IRepository base interface, as in the following code:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

O padrão de repositório facilita os testes da lógica do aplicativoThe Repository pattern makes it easier to test your application logic

O padrão de repositório permite testar facilmente o aplicativo com testes de unidade.The Repository pattern allows you to easily test your application with unit tests. Lembre-se de que os testes de unidade testam apenas o código, não a infraestrutura, assim, as abstrações de repositório facilitam alcançar essa meta.Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.

Como observado em uma seção anterior, é recomendado que você defina e coloque as interfaces de repositório na camada de modelo de domínio para que a camada de aplicativo, como o microsserviço API Web, não dependa diretamente da camada de infraestrutura em que as classes de repositório reais foram implementadas.As noted in an earlier section, it's recommended that you define and place the repository interfaces in the domain model layer so the application layer, such as your Web API microservice, doesn't depend directly on the infrastructure layer where you've implemented the actual repository classes. Fazendo isso e usando a injeção de dependência nos controladores da API Web, você pode implementar repositórios fictícios que retornam dados falsos em vez de dados do banco de dados.By doing this and using Dependency Injection in the controllers of your Web API, you can implement mock repositories that return fake data instead of data from the database. Essa abordagem desacoplada permite que você crie e execute testes de unidade que se focam na lógica do aplicativo sem precisar de conectividade com o banco de dados.This decoupled approach allows you to create and run unit tests that focus the logic of your application without requiring connectivity to the database.

As conexões com bancos de dados podem falhar e, principalmente, executar centenas de testes em relação a um banco de dados é prejudicial por dois motivos.Connections to databases can fail and, more importantly, running hundreds of tests against a database is bad for two reasons. Primeiro, pode demorar muito devido ao grande número de testes.First, it can take a long time because of the large number of tests. Segundo, os registros do banco de dados podem ser alterados e afetar os resultados dos testes, deixando-os inconsistentes.Second, the database records might change and impact the results of your tests, so that they might not be consistent. Testar em relação ao banco de dados não é um teste de unidade, mas sim um teste de integração.Testing against the database isn't a unit test but an integration test. É interessante ter muitos testes de unidade em execução rápida, mas menos testes de integração em relação aos bancos de dados.You should have many unit tests running fast, but fewer integration tests against the databases.

Em termos de separação de interesses para os testes de unidade, a lógica opera em entidades de domínio na memória.In terms of separation of concerns for unit tests, your logic operates on domain entities in memory. Ela considera que a classe de repositório as entregou.It assumes the repository class has delivered those. Depois que a lógica modifica as entidades de domínio, ela considera que a classe de repositório as armazenará corretamente.Once your logic modifies the domain entities, it assumes the repository class will store them correctly. O ponto importante aqui é criar testes de unidade em relação ao seu modelo de domínio e à sua lógica de domínio.The important point here is to create unit tests against your domain model and its domain logic. As raízes de agregação são os limites de consistência principais em DDD.Aggregate roots are the main consistency boundaries in DDD.

Os repositórios implementados no eShopOnContainers dependem da implementação do DbContext do EF Core dos padrões Repositório e Unidade de Trabalho usando seu rastreador de alteração, portanto, eles não duplicam essa funcionalidade.The repositories implemented in eShopOnContainers rely on EF Core’s DbContext implementation of the Repository and Unit of Work patterns using its change tracker, so they don’t duplicate this functionality.

A diferença entre o padrão de repositório e o padrão da classe DAL (classe de acesso a dados) herdadaThe difference between the Repository pattern and the legacy Data Access class (DAL class) pattern

Um objeto de acesso a dados executa operações de acesso e persistência de dados diretamente no armazenamento.A data access object directly performs data access and persistence operations against storage. Um repositório marca os dados com as operações que você deseja executar na memória de um objeto de unidade de trabalho (como no EF ao usar a classe DbContext), mas essas atualizações não são realizadas imediatamente no banco de dados.A repository marks the data with the operations you want to perform in the memory of a unit of work object (as in EF when using the DbContext class), but these updates aren't performed immediately to the database.

Uma unidade de trabalho é conhecida como uma única transação que envolve várias operações de inserção, atualização ou exclusão.A unit of work is referred to as a single transaction that involves multiple insert, update, or delete operations. Simplificando, isso significa que, para uma ação de usuário específica, como o registro em um site, as operações de inserção, atualização e exclusão são tratadas em uma única transação.In simple terms, it means that for a specific user action, such as a registration on a website, all the insert, update, and delete operations are handled in a single transaction. Isso é mais eficiente do que tratar várias transações de banco de dados de uma maneira mais intensa.This is more efficient than handling multiple database transactions in a chattier way.

Essas várias operações de persistência serão executadas mais tarde em uma única ação quando o código da camada de aplicativo executar um comando para isso.These multiple persistence operations are performed later in a single action when your code from the application layer commands it. A decisão de como aplicar as alterações realizadas na memória ao armazenamento de banco de dados real geralmente se baseia no padrão de unidade de trabalho.The decision about applying the in-memory changes to the actual database storage is typically based on the Unit of Work pattern. No EF, o padrão de Unidade de Trabalho é implementado como o DbContext.In EF, the Unit of Work pattern is implemented as the DbContext.

Em muitos casos, esse padrão ou uma maneira de aplicar operações em relação ao armazenamento pode aumentar o desempenho do aplicativo e reduzir a possibilidade de inconsistências.In many cases, this pattern or way of applying operations against the storage can increase application performance and reduce the possibility of inconsistencies. Além disso, ele reduz o bloqueio de transações nas tabelas do banco de dados, porque todas as operações pretendidas são confirmadas em uma única transação.It also reduces transaction blocking in the database tables, because all the intended operations are committed as part of one transaction. Isso é mais eficiente em comparação com a execução de muitas operações isoladas no banco de dados.This is more efficient in comparison to executing many isolated operations against the database. Portanto, o ORM selecionado é capaz de otimizar a execução no banco de dados agrupando várias ações de atualização na mesma transação, em vez de executar várias transações pequenas e separadas.Therefore, the selected ORM can optimize the execution against the database by grouping several update actions within the same transaction, as opposed to many small and separate transaction executions.

Os repositórios não são obrigatóriosRepositories shouldn't be mandatory

Os repositórios personalizados são úteis pelos motivos já citados e essa é a abordagem para o microsserviço de pedidos no eShopOnContainers.Custom repositories are useful for the reasons cited earlier, and that is the approach for the ordering microservice in eShopOnContainers. No entanto, esse não é um padrão essencial a ser implementado em um design DDD ou até mesmo no desenvolvimento para .NET, em geral.However, it isn't an essential pattern to implement in a DDD design or even in general .NET development.

Por exemplo, Jimmy Bogard, ao fornecer comentários diretos para este guia, diz o seguinte:For instance, Jimmy Bogard, when providing direct feedback for this guide, said the following:

Esse provavelmente será meu maior comentário.This’ll probably be my biggest feedback. Eu realmente não sou fã de repositórios, principalmente porque eles ocultam detalhes importantes do mecanismo de persistência subjacente.I’m really not a fan of repositories, mainly because they hide the important details of the underlying persistence mechanism. É por isso que eu também opto pelo MediatR para comandos.It’s why I go for MediatR for commands, too. Posso usar toda a capacidade da camada de persistência e enviar por push todo esse comportamento de domínio para minhas raízes de agregação.I can use the full power of the persistence layer, and push all that domain behavior into my aggregate roots. Geralmente, eu não gosto de usar repositórios fictícios, ou seja, eu ainda preciso aplicar esse teste de integração na situação real.I don’t usually want to mock my repositories – I still need to have that integration test with the real thing. Optar por CQRS significa que realmente não precisamos mais de repositórios.Going CQRS meant that we didn’t really have a need for repositories any more.

Os repositórios podem ser úteis, mas eles não são críticos para o design DDD como são o padrão de Agregação e o modelo de domínio avançado.Repositories might be useful, but they are not critical for your DDD design, in the way that the Aggregate pattern and rich domain model are. Portanto, use o padrão de repositório ou não, conforme achar mais adequado.Therefore, use the Repository pattern or not, as you see fit. De qualquer forma, você usará o padrão de repositório sempre que usar o EF Core, embora, nesse caso, o repositório aborde todo o contexto de microsserviço ou limitado.Anyway, you’ll be using the repository pattern whenever you use EF Core although, in this case, the repository covers the whole microservice or bounded context.

Recursos adicionaisAdditional resources

Padrão de RepositórioRepository pattern

Padrão de unidade de trabalhoUnit of Work pattern