...
Com o projeto criado vamos adicionar os seguintes pacotes via nuget: Tnf.App.Builder, Tnf.App.Dto, Tnf Builder disponível em nosso package source: https://www.myget.org/F/tnf/api/v3/index.json
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using System; using Tnf.Domain.Entities; namespace Tnf.Architecture.Domain.Registration { internalpublic class Person : Entity { public string Name { get; internal set; } public DateTime CreationTime { get; internal set; } public Person() => CreationTime = DateTime.Now; public enum Error { Unexpected = 0, PersonIdMustHaveValue = 1, PersonNameMustHaveValue = 2 } } } |
Note que o exemplo acima está marcado como internal public para que ela não seja exposta para fora da camada de domínio, por exemplo, na camada de Mapper e de Repository.
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using System; using System.Linq.Expressions; using Tnf.Specifications; namespace Tnf.Architecture.Domain.Registration.Specifications { internal class PersonNameShouldHaveValueSpecification : Specification<Person> { public override string LocalizationSource { get; protected set; } => AppConsts.LocalizationSourceName; public override Enum LocalizationKey =>{ get; protected set; } = Person.Error.PersonNameMustHaveValue; public override Expression<Func<Person, bool>> ToExpression() { return (p) => !string.IsNullOrWhiteSpace(p.Name); } } } |
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using Tnf.Architecture.CrossCutting; using Tnf.Architecture.Domain.Registration.Specifications; using Tnf.App.Builder; using Tnf.Localization; namespace Tnf.Architecture.Domain.Registration { internalpublic class PersonBuilder : Builder<Person> { public PersonBuilder(INotificationHandler notification) : base(notification) { } public PersonBuilder(INotificationHandler notification, Person instance) : base(notification, instance) { } public PersonBuilder WithId(int id) { Instance.Id = id; return this; } public PersonBuilder WithName(string name) { Instance.Name = name; return this; } public override Person Build() { base.Validate(); return base.Build(); } protected override void Specifications() { AddSpecification(new PersonNameShouldHaveValueSpecification()); } } } |
O builder acima cria a entidade de forma fluente, adicionando valores a entidade e através do método sobrescrito Build executando todas as regras (especificações) de nossa entidade.
Podemos ver no exemplo acima que chamamos o método Validate da classe pai Internamente é chamado o método Validate da classe Builder para executar todas as Specifications adicionadas no método sobrescrito Specifications() e se a regra das especificações adicionadas não for valida, ele adicionará uma Notification no objeto único do request com a mensagem especificada pelos campos LocalizationSource e LocalizationKey utilizando a classe LocalizationHelper para obter a mensagem de erro através da localização.A classe PersonBuilder também está marcada como internal para que tanto a entidade, regras e builder estejam fechadas dentro de nossa camada de domínio.
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
public interface IPersonRepository : IRepository { int CreatePerson(PersonDtoPerson personDtoperson); } |
Ao definir nosso repositório não passamos a entidade de domínio em sua declaração e sim um DTO que a representa. O uso do DTO é para que a entidade de domínio não seja exposta e assim dessa forma sua integridade seja mantida.
...
Para trabalhar com serviços de domínio, temos a interface IDomainService e uma classe chamada AppDomainService<IRepository> que recebe por parâmetro genérico um repositório.
Tanto a interface como a classe base quando implementadas já utilizam as convenções ao realizar a injeção da dependência automaticamente.
...
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using Tnf.Domain.Services; using Tnf.Dto; namespace Tnf.Architecture.Domain.Interfaces.Services { public interface IRegistrationService : IDomainService { PersonDtoint Register(PersonDtoPersonBuilder dtobuilder); } } |
Podemos perceber a herança da interface IDomainService e o retorno do objeto do método Register PersonDto.Esse objeto de resposta irá retornar o objeto enviado com o id que foi salvo no bancosendo o tipo da chave primária da entidade no método Register, ele retornará só a chave que recebe do repositório pois a camada de Application já possui o Dto de retorno mas não possui o valor da cahve primária.
O método Register recebe um PersonBuilder que a camada de Application irá mandar preenchido e quem chamará o método Build é a própria camada de Domain para que só a essa camada mexa com a entidade.
O código a seguir exemplifica a implementação de nosso serviço de domínio, usando o repositório e o builder criado para a entidade Person.
Bloco de código | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
using Tnf.Architecture.Domain.Interfaces.Services; using Tnf.App.Domain.Services; using Tnf.Dto; namespace Tnf.Architecture.Domain.Registration { public class RegistrationService : AppDomainService<IPersonRepository>, IRegistrationService { private readonly IPersonRepository _repository; public RegistrationService(IPersonRepository repository) : base(repository) { _repository = repository; } public PersonDtoint Register(PersonDtoPersonBuilder dtobuilder) { var builder = new PersonBuilder() .WithName(dto.Name); var person = builder.Build(); if (!Notification.HasNotification()) { return 0; var id = Repositoryreturn _repository.CreatePerson(person); dto.Id = id; } return dto; } } } |
No inicio do método Register definimos o objeto de retorno e logo após criamos o PersonBuilder para chamamos o método Build do Builder para construir nossa entidade através da chamada do método Build.
O método Build nos retorna a estrutura da Entidade e se foi levantado notificações durante o build podemos verificar se existe notificações com o campo exposto pela classe Tnf.App.Domain.Services.AppDomainService Notification.
...