Arquitetura: camadas de serviços em Domain-Driven Design

Quando desenvolvemos usando a abordagem dirigida por modelos, é preciso entender as responsabilidades e local de cada camada, e entre todas as usadas, um tipo é responsável por chamar e expor de forma abstraída e funcional todas as regras de negócio do domínio, elas são chamadas de serviços.

Os serviços podem estar em alguns locais da arquitetura do software, conforme a necessidade e a função exercida para a solução. Segundo Eric Evans, em seu livro Domain-Driven Design, os serviços são divididos ao menos em três camadas:

Aplicativo – Serviço de mais externo

  • Digere as entradas, tais como requisições em txt, xml e Json,
  • Envia mensagens para o serviço de domínio para complementação
  • Espera a confirmação,
  • Decide enviar uma notificação através de um serviço de infraestrutura

Domínio – Regras de negócios

  • Interage com objetos necessários e dispara comportamentos
  • Fornece confirmações do resultado

Infraestrutura – Mensagens e outras comunicações externas

  • Envia mensagens
  • Recebe mensagens

Mas, sem mais delongas, montei uma aplicação bem simples para demonstrar a ideia. São três projetos: Domínio, aplicação e Infraestrutura de mensagens.

Primeiro, vamos criar, no projeto de domínio, uma entidade que representa uma pessoa.

Dentro do mesmo projeto de domínio, vamos adicionar o serviço que salva a pessoa.

No projeto de Infraestrutura, iremos adicionar o serviõ de envio de mensagens.

E por fim, vamos adicionar o serviço na camada de aplicação.

Este projeto está disponível no meu GitHub no endereço a seguir:

  • https://github.com/tiagopariz/DDDServicesLayers

C# with Patterns: Specification

Muitas vezes precisamos abstrair características complexas, tentando simplificar uma consulta Linq para um campo que retorne apenas true ou false, pois isto é muito útil, principalmente quando usamos Entity Framework. O padrão de comportamento especificação busca tornar características, que muitas vezes complexas, em algo mais simples, legível, reusável e de fácil manutenção, com uma abordagem mais elegante.

Além de auxiliar na criação de consulta, Specification pode ser bastante útil em validações, onde uma entidade de domínio precise checar uma série de informações e retornar se ela satisfaz um cenário válido para alguma finalidade específica ou até mesmo persistir os dados no banco. Em resumo, Specification é a união de regras de negócios e expressões boolianas.

Projeto de exemplo

O projeto de exemplo mostra dois usos da abordagem: uma para definir entidades com características específicas e outra para validar entidades, que pode usado juntamente com notificações e eventos. Para isso crie uma solução chamada Specification, e adicione um projeto do tipo Class Library, chamado Specification.Domain.

A interface ISpecification<T> que define o contrato de implementação do método IsSatisfiedBy() e que recebe a especificação baseada em regras de negócio, que são geradas a partir dos métodos And(), AndNot(), Or(), OrNot() e Not() ou uma expresão Linq.

Crie uma pasta chamada Specifications no projeto de domínio, e dentro dela inclua a classe CompositeSpecification, que será a base para as especificações, implementando todos os métodos da interface anterior.

Com a classe base criada, podemos criar as classes de operadores para definir a regra de negócio, que irá retornar uma resposta true ou false. Para isso, adicione as classes a seguir na pasta Specifications:

No projeto de domínio, crie duas pastas chamada Entities e ValueObjects. E em ValueObjects inclua uma classe base para os objetos de valor chamada ValueObject e dentro de Entities inclua uma classe base para as entidades chamada Entity. Estas duas classes terão as propriedades e métodos básicos para as especificações de validação e uma função IsValid que retornará true se os dados preenchidos atenderem os requisitos.

Na pasta ValueObjects teremos uma classe de Email que herda de ValueObject, e na pasta de Entities as classes Category e Person e ambas herdam de Entity.

Neste ponto, a propriedade ValidSpecification ainda não possui suas especificaçãos que validam pessoa e o e-mail, e para isso temos que criar as classes com as regras de negócio.

Entre na pasta Specification, e dentro dela crie uma pasta chamada ValueObjects e neste diretório inclua uma classe chamada EmailValidSpecification que herda de CompositeSpecification.

Na pasta Entities de Specification, inclua uma classe chamada PersonNameValidSpecification, que irá validar o nome da pessoa.

Com as especificação de validação de e-mail e de nome prontas, podemos adicionar uma especificação que irá validar a pessoa como um todo, e para isso inclua uma classe chamada PersonValidSpecification na pasta Entities de Specifications.

Ainda temos um especificação para definir se uma pessoa é cliente, que pode ser usada em filtros de listas baseadas em Linq, portanto inclua o objeto PersonCustomerSpecification na pasta Entities de Specifications.

Projeto de Apresentação

Crie um projeto do tipo Class Library, chamado Specification.Domain, para que, via console, testarmos algumas formas de usar as especificações. E então, no método Main da classe Program, crie uma lista de pessoas através de uma variável do tipo List.

A seguir vamos listar apenas os clientes, usando a especificação PersonCustomerSpecification.

Neste outro exemplo, retornarmos todas as pessoas que são parceiras, usando uma expressão Linq:

A seguir é usada uma especificação que filtra apenas entidades válidas usando a especificação pronta, mas esta operação pode ser feita também chamando o método IsValid da própria entidade.

Toda a classe Program pode ser vista a seguir, com outros exemplos:

E aqui temos a saída completa do console.

Conclusão

Specification é um padrão bastante flexível, tornando as regras de negócio do domínio mais claras. Entretanto, as validações podem ser simplificadas, usando pacotes de terceiros, como por exemplo o Flunt, que une esta abordagem com a Domain Notification, e pode residir em uma camada transversal do projeto.

O projeto completo pode ser baixado no GitHub:

https://github.com/tiagopariz/Specification

Referências

C# with Patterns: Notification

Segundo Martin Fowler, Notificação é um objeto que coleta dados sobre erros e outras informações na camada de domínio e que comunica com as outras camadas, principalmente com a camada de apresentação.

O padrão Notification, ou notificação, é uma solução elegante para tratar erros de sistema desnecessários, pois exceção, além de causar uma parada abrupta na execução de todo o programa, tem um custo muito alto para o processador. Por isso, usando uma lista de notificações é possível coletar os problemas previsíveis, listar, classificar e informar às outras camadas, ou até mesmo ao usuário, de uma só vez sobre todos os problemas que ele precisa corrigir antes de continuar.

A testabilidade do código também aumenta, pois é possível definir erros, alertas e mensagens padrões para determinadas situações sem lançar exceções, ou seja, agiliza o teste e fica muito mais claro o que o sistema espera de entrada. Ainda é possível combinar esta abordagem com outros padrões, como Specification e Design By Contract e criar um tratamento poderoso e profissional de validações.

O Artigo de Martin Fowler

O artigo original sobre Notification foi escrito em 2004, e faz parte do livro Patterns of Enterprise Application Architecture de 2003, e nele, Fowler até demonstra como usar o padrão usando C# em uma aplicação windows, mas para que fosse mais fácil entender hoje, eu atualizei a sintaxe usando C# 7.0 e desenvolvi alguns métodos que ele não incluiu no artigo, portanto, se você quiser acessar é só entrar no meu repositório Notification by Martin Fowler do GitHub e conferir a abordagem descrita por ele em uma aplicação console.

O Flunt e o Design By Contract de André Baltieri

André Baltieri, MVP da Microsoft, desenvolveu um pacote Nugget, chamado originalmente de Fluent Validator, no qual tive o prazer de contribuir, e que mais tarde foi rebatizado de Flunt. O Flunt é um sistema poderoso e extensível de validação e notificações em domínios, reduzindo “Ifs” e Testes, contribuindo na otimização do tempo,  podendo assim focar na codificação e na regra de negócio do seu domínio do projeto.

Para conhecer os detalhes deste pacote fenomenal e seu código fonte, é só ler o artigo Design By Contracts e também assistir os vídeos do canal dele. Posso garantir que será de grande proveito!

O Meu Projeto de Domain Notification

É possível usar as notificações de um modo simples, apenas para atender um necessidade específica, criando apenas uma lista de mensagens, mas este padrão é poderoso o suficiente para que seja usado em domínios complexos, e combinado com outras abordagens. O projeto que desenvolvi tem um nível de complexidade médio, por este motivo já indiquei o Flunt, pois é uma forma de usar o conceito para validações com o mínimo de esforço e máxima confiabilidade, e assim já terá grandes ganhos, como a redução do uso de exceções e Ifs. Mas se você é como eu, que gostaria de usar ao máximo o padrão e ter controle sobre todo o seu funcionamento, vai ver como desenvolver e usar neste exemplo prático.

Domain Driven Design – DDD

Quase todos os meus projetos são desenvolvidos usando a abordagem de arquitetura DDD, ou seja, quase sempre terá um projeto focado no domínio, assim como o uso de objetos de valor e pelo menos as camadas de domínio, aplicação e apresentação. Se você não faz ideia do que estou falando, não há problemas, é possível entender este artigo mesmo assim, mas se quiser saber mais sobre desenvolvimento dirigido ao domínio, segue a indicação de um excelente vídeo do Eduardo Pires, MVP da Microsoft.

A solução

Abra o Visual Studio, crie uma solução vazia chamada DomainNotification, inclua três Solutions folder chamadas Domain, Application e Presentation.

Na pasta Domain inclua um projeto do tipo Class Library chamado DomainNotification.Domain, na pasta Application inclua um projeto do tipo Class Library chamado DomainNotification.Application e finalmente na pasta Presentation inclua um projeto do tipo Console application chamado DomainNotification.Prompt e defina como Set as StartUp project. A estrutura deve parecer como a imagem a seguir.

Projeto de Domínio

O projeto de domínio será onde residirão as entidades principais, assim como suas classes bases abstratas, também conhecidas como camada de Supertypes. Mas vamos começar criando as estruturas e classes de notificação, que estarão nesta camada.

No projeto de domínio, inclua uma pasta chamada Interfaces, e dentro desta pasta, outra chamada Notifications. Em Notifications, inclua uma interface chamada IDescription e INotification.

A interface INotification tem como objetivo definir um contrato para criação de notificações, definindo o mínimo que um classe precisa ter, ela facilita também o uso de injeção de dependência, podendo ajudar na redução de acoplamento entre projetos.

IDescription define um contrato mínimo para uma descrição de uma notificação lançada, assim como sobrescreve o método ToString() para que possa ser usada como um texto simples.

Ainda no projeto de domínio, inclua uma pasta chamada Notifications, e dentro desta pasta, inclua duas classes abstratas, uma chamada Description e outra chamada Notification.

A classe Description é abstrata, e tem como objetivo ser usada como herança para a classe que será a descrição da notificação em si, assim como a classe Notification que será usada como Supertype para a classe de notificação.

Estas quatro classes sustentam todo o core das notificações, fornecendo o mínimo para ser usado em uma entidade de domínio, por exemplo. mas também permite uma extensão do recurso, podendo adicionar outras peculiaridades necessárias.

Erros como notificações

Um dos principais usos de notificações é para tratamento de erros e validações de dados, e para isso, é economizado então uma parada e o lançamento desnecessário de uma exceção do Framework. A proposta aqui é criar um exemplo que usa uma interface e algumas classes básicas: ILevel, Error, ErrorDescription e classes para a severidade do erro.

A classe Error herda da classe abstrata Notification, mas estende a possibilidade de listar erros com descrição e nível, assim como verificar erros somente de um nível de severidade ou simplesmente checar se há erros de um tipo específico ou de modo geral.

No projeto de domínio, na pasta de Interfaces crie uma pasta chamada Errors e dentro dela inclua a interface a seguir:

No projeto de domínio, crie uma pasta chamada Errors e dentro dela inclua as três classes a seguir de nível de severidade:

A classe ErrorDescription recebe as informações básicas de uma notificação e ainda um objeto do tipo ILevel com a severidade do erro para incluir na classe Error.

Com estas classes, personalizamos a notificação para uso com exceções, no qual agora poderá ser usada por outra classe que queira listar e comunicar entre camadas este tipo de informação.

Validando uma entidade

Já temos a estrutura básica para que uma entidade de domínio possa usar este recurso, e ainda ter a possibilidade de verificar se ela é válida ou não para ser usada em outra lógica e/ou salvar no banco de dados. Conseguimos implementar adicionando uma propriedade e dois métodos.

A propriedade Errors é do tipo Error, que é uma instância de notificação, será aqui que as validações serão armazenadas e consultas.

Para permitir que as validações possam ser globais, específicas ou opcionais, criamos um método chamado Validate, que só pode ser disparado na entidade, ou seja, se alguma validação que a maioria está usando não fizer sentido para o objeto pode ser ignorada ou se houver outras validações que são tão específicas para aquela entidade, ela pode ser criada e chamada na entidade.

E para fechar as características mínimas para uma classe poder usar as notificações de erros, temos uma função chamada IsValid, que apenas verifica se há ao menos um erro de nível Critical. Erros dos níveis Warning e Information não tornam a classe inválida neste exemplo.

Porém, para não ficar validando coisas que são comuns ou que a maioria das entidades precisam validar, é possível armazenar estas verificações direto na classe base abstrata do domínio, e para isso criei uma região na classe chamada Validations, onde foi colocada todas as funções que são globais ou bastante usadas. Mesmo assim é preciso chamar na classe principal, pois elas são opcionais, mas estão prontas para usar. Para exemplificar o cenário, escrevi uma função para validar Guid e outra para Nomes, assim como duas descrições de erros, em uma região chamada Errors, com suas devida mensagens e níveis de severidade.

No projeto de domínio, crie uma pasta chamada ValueObjects, e dentro dela inclua as classes a seguir:

Crie outra pasta na raiz do projeto chamada Entities, e dentro dela inclua as classes a seguir:

Comandos, Aplicação e Console

Para testar as notificações, vamos criar os comandos, uma camada de serviço de aplicação e um aplicativo console para simular uma entrada de dados e visualizar os retornos. A montagem desta parte do exemplo não é foco do artigo, se quiser saber os detalhes destes padrões, sugiro que busque informações sobre Commands, Application Layer e Presentation Layer.

Comandos

Na raiz do projeto de domínio, crie uma pasta chamada Commands, e nela inclua uma classe abstrata chamada Command e outra concreta chamada SavePerson, como a seguir:

Com os comandos, a camada de domínio está concluída, agora precisamos criar uma camada de aplicação, que será consumida pela camada de apresentação.

Aplicação

A camada aplicação, conhecida como Service Aplication ou Application Layer, coordena a chamada das camadas de apresentação com o domínio e repositórios. Para criar esta camada, no projeto DomainNotification.Application crie uma pasta chamadas Services, e dentro dela inclua uma classe abstrata chamada Service e outra concreta chamada PersonService, como seguem:

Apresentação

A camada de apresentação poderia ser uma página Web, um App Mobile, um Windows Forms, mas para simplificar o nosso exemplo, vamos usar uma Aplicação Console. Esta aplicação vai receber dois campos que será o Nome e o E-Mail, no qual esperamos que o domínio valide e retorne as notificações. As informações serão transportadas através de DTOs (Data Transfer Objects), que podem o não representar uma entidade de domínio. No nosso caso será a representação da classe Person.

No projeto DomainNotification.Prompt crie uma pasta chamada Dto, e dentro dela inclua uma classe chamada PersonDto, como segue:

E por fim, altere a classe Program para que consuma a camada de aplicação:

A aplicação está concluída, agora podemos testar, como por exemplo, não preencher nenhum campo.

Se preencher o nome corretamente, mas preencher o e-mail de forma incorreta, uma notificação será exibida, sem lançar exceção, informando o problema.

Assim como um notificação de sucesso quando os dados são inseridos corretamente.

Conclusão

Domain Notification ou simplesmente Notification é uma padrão de arquitetura que economiza processamento e torna a aplicação mais clara e versátil, podendo trabalhar com outros padrões e tipos de validação.

Acesse o projeto completo no GitHub:

https://github.com/tiagopariz/DomainNotification

C# with Patterns: Domain Core

Há uma série de abordagens e padrões que podemos usar dentro de uma camada de domínio para que as informações sejam recebidas e enviadas para outras camadas de forma correta e eficaz, e para isso usamos padrões como Command, Events, Notification, Specification, etc.

Pensando desta forma, podemos usar uma abordagem que separe as classes e interfaces genéricas do domínio em um projeto central, chamado Domain Core, onde tudo que for genérico ou central deve estar aqui, sobrando para o projeto de domínio apenas as classes, interfaces e responsabilidades específicas das entidades e suas regras de negócio. Outra vantagem, é a reutilização de código, sendo que tudo que for genérico, estará neste projeto.

O exemplo a seguir não tem a intenção de mostrar um aplicativo funcionando com os padrões e abordagens necessárias para um projeto DDD em C#, mas apenas demonstrar com seria uma organização de códigos e suas responsabilidades dentro da proposta, além do mais, também pode ser considerado como uma sugestão e não como um modelo a ser seguido a risca. Portanto, o que tá em discussão não é necessariamente um modelo certo ou errado, mas apenas uma forma de organizar os arquivos do projeto e facilitar a implementação dirigida ao domínio.

Podemos dizer que o core é um núcleo compartilhado do domínio principal e subdomínios e talvez até de domínios genéricos.

Projeto Domain.Core

O projeto Core do domínio deve estar na mesma Solution folder do domínio, e se for um pacote ou DLL deverá ser referenciado pelo domínio. Para começar, siga os passos:

  1. Crie uma solução vazia chamada DomainCore,
  2. Adicione uma Solution folder chamada Domain
  3. Dentro de Domain adicione um projeto do tipo Class Library chamado DomainCore.Domain.Core,
  4. Na raiz do projeto, crie três pastas chamadas Interfaces, Entities e ValueObjects,
  5. Na pasta Interfaces, inclua duas subpastas chamadas Entities e ValueObjects,
  6. Remova todas as referências, para que o projeto esteja o mais limpo possível, e adicione mais tarde conforme for necessário.

Agora temos uma estrutura básica de um projeto base de domínio, ou seja, a aplicação deve parecer como a imagem a seguir:

O objetivo até aqui é manter uma arquitetura funcional e simples, apesar de haver sim alguma complexidade para que tenhamos uma arquitetura decente no início, mas deve facilitar a manutenção no futuro.

Projeto Domain

O projeto que é o domínio de fato deve estar na mesma Solution folder do domínio também, ele pode fazer referência ao core através de uma DLL ou instalação de pacote NuGet. Para montar a camada de domínio siga os passos:

  1. Dentro de Domain adicione um projeto do tipo Class Library chamado DomainCore.Domain,
  2. Na raiz do projeto, crie três pastas chamadas Interfaces, Entities e ValueObjects,
  3. Na pasta Interfaces, inclua duas subpastas chamadas Entities e ValueObjects,
  4. Remova todas as referências, para que o projeto esteja o mais limpo possível, e adicione mais tarde conforme for necessário.
  5. Adicione a referência para o projeto DomainCore.Domain.Core.

Agora temos uma estrutura para as entidades específicas de um projeto de domínio, a aplicação deve parecer como a imagem a seguir:

Interfaces e entidades bases

Vamos incluir os seguintes itens em DomainCore.Domain.Core:

  1. DomainCore.Domain.Core/Interfaces/Entities/IEntity.cs
  2. DomainCore.Domain.Core/Interfaces/ValueObjects/IValueObject.cs
  3. DomainCore.Domain.Core/Entities/Entity.cs
  4. DomainCore.Domain.Core/ValueObjects/ValueObject.cs

Vamos incluir os seguintes itens em DomainCore.Domain:

  • DomainCore.Domain/Interfaces/Entities/IPerson.cs
  • DomainCore.Domain/Interfaces/ValueObjects/IEmail.cs
  • DomainCore.Domain/Entities/Person.cs
  • DomainCore.Domain/ValueObjects/Email.cs

O projeto final deve parecer como este:

Uma organização simples, e que torna a camada de domínio mais limpa e isola os objetos genéricos e padrões para todas as entidades. Facilita também o reuso da arquitetura e correções que afetam muitos objetos.

Serve para abrir também uma discussão acerca da abordagem DDD, pois pode não haver um modelo ideal, mas podemos juntos encontrar caminhos para projetos mais profissionais e mais fáceis de escalar e manter.

O projeto completo pode ser baixado no GitHub em https://github.com/tiagopariz/DomainCore.

C# DDD: conhecendo os Domain Events

Ao desenvolver um projeto em C#, usando a abordagem DDD, após criar as entidades e seus comportamentos, muitas vezes precisamos criar eventos que são disparados após um comportamento específico do domínio.

Neste caso, nós temos uma abordagem chamada Domain Events, que após a executar uma função, é disparada um ou mais comandos que são executados após, ou até mesmo em segundo plano.

O Projeto

Para criar o projeto, se pressupõe que você já entende de abordagem por domínio, A estrutura conta com três projetos: um projeto para o domínio, outro console para exibir os dados e disparar os eventos e um projeto de IoC, ou seja, para usar Inversão de Controle ou Injeção de Dependência.

Criando os Projetos

Abra o Visual Studio 2017, e crie uma nova solução chamada DDDDomainEvents, e inclua um projeto tipo Class Library chamado DDDDomainEvents.Domain,  adicione outro projeto do tipo Class Library chamado DDDDomainEvents.IoC e por fim adicione um projeto do tipo Console App chamado DDDDomainEvents.Prompt. Todos os projetos foram criado em .NET Framework 4.7.

No projeto DDDDomainEvents.Domain, crie uma pasta chamada Entities, e dentro desta pasta, adicione uma classe chamada Person.cs.

No mesmo projeto, crie uma pasta chamada Events, e dentro desta pasta, adicione uma interface chamada IDomainEvent.cs.

Na pasta Events, adicione uma classe chamada PersonRegisteredEvent.cs.

Crie uma pasta chamada Handlers, e dentro desta pasta, adicione uma interface chamada IHandler.cs.

Na pasta Handlers, adicione uma classe chamada PersonSave.cs.

No projeto DDDDomainEvents.IoC adicione uma referência ao projeto DDDDomainEvents.Domain.

Abra o Package Manager Console, e instale o SimpleInjector no projeto DDDDomainEvents.IoC.

Adicione uma classe chamada BootStrapper.cs.

No projeto DDDDomainEvents.Prompt adicione uma referência aos projetos DDDDomainEvents.Domain e DDDDomainEvents.IoC.

Abra o Package Manager Console, e instale o SimpleInjector no projeto DDDDomainEvents.Prompt.

Altere a classe Program.cs para que ela dispare o evento.

Defina o projeto DDDDomainEvents.Prompt como o projeto padrão, clicando o botão direito sobre ele e em seguida clicando em Set as StartUp Project.

Acesse o código da solução:

github.com/tiagopariz/DDDDomainEvents

C# with Patterns: Adapters

Na série C# with Patterns, que mostra como usar padrões de arquitetura em programação com C#, vamos conhecer o padrão de adaptadores, ou Adapters Pattern.

Continuar lendo C# with Patterns: Adapters