Cuide bem do seu domínio! Parte 4

Seguindo com a série de posts sobre TDD, vamos implementar a última regra de negócios do nosso projeto que valida a idade do aluno de acordo com a faixa etária para o curso no momento da matrícula. Darei sequência a partir do código fonte disponibilizado na parte 3 deste tutorial.

Passo 23 - Vamos começar criando um nova especificação para a entidade Matricula. Crie uma nova classe na pasta \Specifications\Matriculas\MatriculaFaixaEtariaPorCursoSpecification.cs

TDD-ASP-NET-16

MatriculaFaixaEtariaPorCursoSpecification.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MatriculaFaixaEtariaPorCursoSpecification : ISpecification
{
    private readonly IMatriculaRepository _matriculaRepository;
 
    public MatriculaFaixaEtariaPorCursoSpecification(IMatriculaRepository matriculaRepository)
    {
        _matriculaRepository = matriculaRepository;
    }
 
    public bool IsSatisfiedBy(Matricula matricula)
    {
        return _matriculaRepository.ValidarFaixaEtariaPorCurso(matricula.AlunoId, matricula.CursoId); //add novo método
    }
}

Essa classe receberá os ID's de Aluno e Curso e através destes irá obter a idade do aluno e o curso informados no momento do cadastro da matrícula. Caso a idade do aluno obedeça ao critério exigido retornará true, caso contrário, false.

Passo 24 - Vamos adicionar o método ValidarFaixaEtariaPorCurso na interface IMatriculaRepository.
IMatriculaRepository.cs

1
2
3
4
5
public interface IMatriculaRepository : IRepository
{
    Matricula ObterMatriculaAlunoCurso(Guid aluno, Guid curso);
    bool ValidarFaixaEtariaPorCurso(Guid aluno, Guid curso); // added- criar regra do negócio na classe de serviços
}

Passo 25 - Adicione a instância da especificação nova na classe \Validations\Matriculas\MatriculaCadastroValidation.cs

1
2
3
4
5
6
7
8
9
10
11
public class MatriculaCadastroValidation : Validator
{
	public MatriculaCadastroValidation (IMatriculaRepository matriculaRepository)
	{
	    var matriculaUnica = new MatriculaUnicaSpecification(matriculaRepository);
            var matriculaFaixaEtaria = new MatriculaFaixaEtariaPorCursoSpecification(matriculaRepository); //added
 
	    base.Add("matriculaUnicaRule", new Rule(matriculaUnica, "Aluno já possui matrícula para esse curso!"));
            base.Add("matriculaFaixaEtariaRule", new Rule(matriculaFaixaEtaria, "Aluno fora da faixa etária para esse curso!")); //added
	}
}

Finalizamos em relação às classes de validação do specification pattern.
- Mas a lógica da regra de negócio ainda não foi implementada? Sim, de fato! Até aqui nós garantimos apenas que ela seja instanciada junto com todas as validações já implementadas e a lógica será implementada no método ValidarFaixaEtariaPorCurso() da classe de serviço.
Já podemos testar a nova regra simulando um retorno true ou false do método ValidarFaixaEtariaPorCurso junto com o método já testado anteriormente, ObterMatriculaAlunoCurso, que evita duplicidade de Aluno x Curso.

Passo 26 - Altere a classe de teste MatriculaCadastroTest.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Linq; //added Linq reference
 
[TestClass]
public class MatriculaCadastroTest
{
    public Matricula Matricula { get; set; }
 
    [TestMethod]
    public void Matricula_Validation_True()
    {
        Matricula = new Matricula()
        {
            AlunoId = new Guid("b2c7e6da-4086-4bf3-9c2f-225852da34c3"),
            CursoId = new Guid("b7b6c5a1-2cea-4ee2-9967-b34d4f76ece6")
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterMatriculaAlunoCurso(Matricula.AlunoId, Matricula.CursoId)).Return(null);
        stubRepository.Stub(s => s.ValidarFaixaEtariaPorCurso(Matricula.AlunoId, Matricula.CursoId)).Return(true); //added
 
        var matriculaValidation = new MatriculaCadastroValidation(stubRepository);
        Assert.IsTrue(matriculaValidation.Validate(Matricula).IsValid);
    }
 
    [TestMethod]
    public void Matricula_Validation_False()
    {
        Matricula = new Matricula()
        {
            AlunoId = new Guid("b2c7e6da-4086-4bf3-9c2f-225852da34c3"),
            CursoId = new Guid("b7b6c5a1-2cea-4ee2-9967-b34d4f76ece6")
        };
        var stubRepository = MockRepository.GenerateStub();
        stubRepository.Stub(s => s.ObterMatriculaAlunoCurso(Matricula.AlunoId, Matricula.CursoId)).Return(Matricula);
        stubRepository.Stub(s => s.ValidarFaixaEtariaPorCurso(Matricula.AlunoId, Matricula.CursoId)).Return(false); //added
 
        var matriculaValidation = new MatriculaCadastroValidation(stubRepository);
        var result = matriculaValidation.Validate(Matricula);                                    //added
 
        Assert.IsFalse(matriculaValidation.Validate(Matricula).IsValid);
        Assert.IsTrue(result.Erros.Any(e => e.Message == "Aluno já possui matrícula para esse curso!")); //added
        Assert.IsTrue(result.Erros.Any(e => e.Message == "Aluno fora da faixa etária para esse curso!")); //added
    }
}

Passo 27 - Adicione a propriedade ValidationResult na entidade Matricula

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Matricula
{
    public Matricula()
    {
        MatriculaId = Guid.NewGuid();
    }
    public Guid MatriculaId { get; set; }
    public Guid CursoId { get; set; }
    public Guid AlunoId { get; set; }
    public bool Status { get; set; }
    public virtual Curso Curso { get; set; }
    public virtual Aluno Aluno { get; set; }
    public ValidationResult ValidationResult { get; set; } //added
}

Vamos a classe de serviço que retornará true ou false de acordo com a faixa etária estabelecida por curso na parte 1 do tutorial.

Passo 28 - A classe de serviço do domínio poderá tratar n regras de negócio. Ela herdará a interface IMatriculaRepository e portanto deverá implementar os metódos de validação, bem como os métodos CRUD. Crie na pasta \Interfaces uma subpasta chamada \Service - e dentro dela a interface IMatriculaService.cs.
TDD-ASP-NET-17
IMatriculaService.cs

1
2
3
4
5
6
7
8
9
10
11
public interface IMatriculaService : IDisposable
{
    Matricula Adicionar(Matricula matricula);
    Matricula ObterPorId(Guid id);
    IEnumerable ObterTodos();
    Matricula Atualizar(Matricula matricula);
    void Remover(Guid id);
 
    Matricula ObterMatriculaAlunoCurso(Guid aluno, Guid curso);
    bool ValidarFaixaEtariaPorCurso(Guid aluno, Guid curso);
}

Passo 29 - Na pasta \Services, crie a classe de serviço do domínio - MatriculaService.cs
MatriculaService.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class MatriculaService : IMatriculaService
{
    private readonly IMatriculaRepository _matriculaRepository;
    private readonly ICursoRepository _cursoRepository;
    private readonly IAlunoRepository _alunoRepository;
 
    public MatriculaService(IMatriculaRepository matriculaRepository, 
                            ICursoRepository cursoRepository,
                            IAlunoRepository alunoRepository)
    {
        _matriculaRepository = matriculaRepository;
        _cursoRepository = cursoRepository;
        _alunoRepository = alunoRepository;
    }
 
    public Matricula Adicionar(Matricula matricula)
    {
 
        matricula.ValidationResult = new MatriculaCadastroValidation(_matriculaRepository).Validate(matricula);
        if (!matricula.ValidationResult.IsValid)
        {
            return matricula;
        }
 
        matricula.ValidationResult.Message = "Matrícula cadastrada com sucesso!";
        return _matriculaRepository.Adicionar(matricula);
    }
 
    public Matricula ObterPorId(Guid id)
    {
        return _matriculaRepository.ObterPorId(id);
    }
 
    public IEnumerable ObterTodos()
    {
        return _matriculaRepository.ObterTodos();
    }
 
    public Matricula Atualizar(Matricula matricula)
    {
        return _matriculaRepository.Atualizar(matricula);
    }
 
    public void Remover(Guid id)
    {
        _matriculaRepository.Remover(id);
    }
 
    public Matricula ObterMatriculaAlunoCurso(Guid aluno, Guid curso)
    {
        return _matriculaRepository.ObterMatriculaAlunoCurso(aluno, curso);
    }
 
    public bool ValidarFaixaEtariaPorCurso(Guid aluno, Guid curso)
    {
        int idade = _alunoRepository.ObterIdade(aluno);
        string codigo = _cursoRepository.ObterCodigo(curso);
        switch (codigo)
        {
            case "1EF":   //1a serie Ensino Fundamental
                return ((idade == 6 || idade == 7) ? true : false);
            case "2EF":
                return ((idade == 7 || idade == 8) ? true : false);
            case "3EF":
                return ((idade == 8 || idade == 9) ? true : false);
            case "4EF":
                return ((idade == 9 || idade == 10) ? true : false);
            case "5EF":
                return ((idade == 10 || idade == 11) ? true : false);
            case "6EF":
                return ((idade == 11 || idade == 12) ? true : false);
            case "7EF":
                return ((idade == 12 || idade == 13) ? true : false);
            case "8EF":
                return ((idade == 13 || idade == 14) ? true : false);
            case "9EF":
                return ((idade == 14 || idade == 15) ? true : false);
            case "1EM":
                return ((idade == 15 || idade == 16) ? true : false);
            case "2EM":
                return ((idade == 16 || idade == 17) ? true : false);
            case "3EM":
                return ((idade == 17 || idade == 18) ? true : false);
            default:
                return false;
        }
 
    }
 
    public void Dispose()
    {
        _matriculaRepository.Dispose();
        GC.SuppressFinalize(this);
    }
}

A classe acima recebe por injeção de dependência instância das classes de aluno e curso chamando métodos que retornam da base de dados um int representando a idade do aluno e a string código do curso - linhas 56 e 57. O método que retorna o código do curso por exemplo, deverá usar um split A partir dessas duas variáveis, definimos a regra de negócio que estabelece idade por curso - linhas 60 a 85.

As classes de serviço para as entidades Aluno e Curso também são necessárias para atendermos ao escopo proposto. O projeto final contemplando todos os serviços do domínio está disponível no GitHub.

Conforme sugerido pelo DDD, nossa camada de domínio não possui referências a nenhuma outra camada. Abstrai inclusive o tipo de ORM que será implementado nas classes de repositório na camada de Infraestrutura.

Através do modelo proposto pudemos criar as nossas entidades e testar o conjunto das regras de negócio com base em especificações únicas para cada regra. A manutenção dessas especificações no futuro torna-se muito prática e elas podem ser testadas isoladamente quando modificadas.

Particularmente, considero uma boa forma de iniciar o desenvolvimento com foco no domínio. Seguimos as recomendações do DDD aplicando conceitos como a abstração das camadas com Injeção de Dependência, testes de unidade com Specification Pattern e classes POCO para as entidades.

Abaixo links para baixar o código.
Código fonte GitHub - Parte 3
Código fonte GitHub- Parte 4

Até a próxima!
Thanks for reading!

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This blog is kept spam free by WP-SpamFree.