Domain-Driven Design

by alisson.vale 26/11/2006 22:58:09

Dos livros na área de design lançados nos últimos meses, o que mais me chamou a atenção foi o livro do Jimmy Nilsson: Applying Domain-Driven Design and Patterns. Depois de ler e reler alguns capítulos, já dá pra dizer que o livro é muito bom. O autor explica sua solução de design para uma série de problemas recorrentes que nós desenvolvedores passamos toda vez que queremos montar um esquema arquitetural que privilegie flexibilidade, extensibilidade, desacoplamento, facilidade para escrever testes de unidade e para manter o código fácil de ler e manter.

Na verdade, não há muitas novidades para quem é fã de arquitetura e, como eu, acha que – a não ser que algo novo apareça – o Domain Model pattern (Fowler, PoEAA) é a solução ideal para pelo menos 90% das soluções de softwares empresariais que desenvolvemos hoje em dia. Mas vale a pena reforçar algumas idéia do livro, das quais eu compartilho na totalidade.

Quando se fala em Domain Driven Design, o conceito-chave é o Foco no Domínio do problema, o que leva a premissa de que código que resolve problema de domínio não se mistura com código que resolve problema de software. Em outros termos, eu devo ser capaz de codificar soluções para o domínio do negócio sem absolutamente nenhuma dependência com nenhuma estrutura ou infra-estrutura de software.

Isso resulta em uma solução de software com uma estrutura de camadas arquiteturais parecida com a figura apresentada a seguir:

ddd-schema.png

A camada de Interface com o Usuário (UI) e a camada Application são simples e não há nada de diferente do que já estamos acostumados a fazer. Repare que não há nenhuma seta saindo de Domain. Isso revela que Domain não depende de ninguém. É totalmente isolada e, em tese, poderia funcionar sem nenhuma limitação imposta pela infra-estrutura de software adotada.

Veja um exemplo:

   public class JurosSimples: EstrategiaCalculoJuros

    {

        Money _principal;

        decimal _taxa;

        int _numeroPeriodos;

        public JurosSimples(Money principal, decimal taxa, int numeroPeriodos)

        {

            _principal = principal;

            _taxa = taxa;

            _numeroPeriodos = numeroPeriodos;

        }

        public override Money Calcular()

        {

            return _principal.Valor * _taxa * _numeroPeriodos;

        }

    }

Será que esse código vai mudar se estivermos trabalhando em uma aplicação web ou desktop? Windows, linux ou Mac? Que acessa um banco de dados MSSql, SqlLite ou Oracle? Ou que persiste e recupera dados da memória, de arquivos-texto ou xml? É claro que não. Mas esse exemplo não é muito bom. É óbvio até para o mais novato dos desenvolvedores que eu não precisaria mudar uma classe de cálculo de juros só porque mudei o banco de dados ou o sistema operacional. Vamos colocar de outra maneira então. Imagine alguns milhares de classes de regras de negócio. Inclua nessa lista aquelas que precisam buscar a lista de clientes e mostrar na tela com a maior variedade de filtros e opções, além daquelas que juntam 7 ou 8 tabelas do seu banco de dados para montar uma estrutura de dados.  No DDD, todas elas precisam funcionar sem nenhum acoplamento  com a infra-estrutura de software adotada.

Assim, a forma como o modelo de domínio vai  ser utilizada para que seu sistema funcione é um problema de software e não de negócio. O que importa para o domain model é como o negócio estará sendo representado e como suas regras estarão descritas. Quanto mais as duas coisas estiverem desassociadas melhor será para o esquema arquitetural do seu sistema.

Confesso que realmente é difícil de enxergar o funcionamento disso sem que possamos “ver com os nossos próprios olhos”. Essa é a parte mais difícil. A chave dessa solução está na implementação da camada de infra-estrutura. É ela que possibilita toda essa independência do Domain Model. Uma camada de infra-estrutura com falhas de design inviabilizará o modelo DDD.

Muitos conceitos, técnicas e padrões de design estarão por trás trabalhando para que a arquitetura DDD funcione. Estou falando de:

·         Persistence Ignorance (PI) (ou POCO/POJO em termos mais populares);

·         Repository Pattern;

·         Query Object Pattern;

·         UnitOfWork Pattern;

·         Aggregate Pattern (Eric Evans DDD);

·         Dependency Injection;

·         Mocks, Stubs and Fakes;

·         Patterns de Domínio (Specification Pattern, Factory, Strategy, State e outros). Lembrando que, segundo o autor, os patterns de domínio são tratados de uma forma um pouco diferente dos patterns GoF, apesar de terem a mesma implementação, podem ter semântica e objetivos diferentes.

Regras de Negócio

Um dos pontos altos do livro é o capítulo 7, onde são discutidas uma série de questões sobre estruturação e uso de regras de negócio no Domain Model. Veja no snippet de código apresentado abaixo como eu estruturei algumas regras de persistência em uma classe Aluno, seguindo o modelo que o Nilsson apresentou nesse capítulo:

namespace DomainModel.Academico

{

    public class Aluno

    {

        private List<Rule> _persistenceRules = new List<Rule>();

 

        public Aluno()

        {

            DefinePersistenceRules();

        }

       

        public void DefinePersistenceRules()

        {

            _persistenceRules.Add(new InformacaoRequerida("NomeCompleto", this));

            _persistenceRules.Add(new TamanhoMaximoDeCampo(100, "NomeCompleto", this));

            _persistenceRules.Add(new FormatacaoDeEmail("Email", this));

            _persistenceRules.Add(new TamanhoMaximoDeCampo(150, "Email", this));

            _persistenceRules.Add(new MatriculaPreExistente(this));

            _persistenceRules.Add(new CPFPreExistente(this));

        }

 

        public ListaDeRegras CollectBrokenRules()

        {

            return RuleBase.CollectBrokenRules(_regras);

        }

    }

}

Veja que a validação de um objeto de negócio quanto a persistência pode soar totalmente natural dessa forma para quem está lendo o código sob a ótica de domínio. Nada de queries, acessos ao NHibernate, DAOs, etc. É claro que em algum momento isso será feito, mas não de forma acoplada ao objeto de domínio.

Veja que esse design permite acoplar diferentes listas de regras para um objeto de negócio, podendo ser validadas em momentos diferentes e com propósitos diferentes.

Esse assunto é bastante interessante e pode render muita discussão. Assim que novas idéias forem surgindo estarei publicando por aqui.

Tags:

Design

Mazelas do Singleton

by alisson.vale 22/11/2006 14:39:09

O Singleton é um dos design patterns mais simples de se entender e também mais fácil de ser implementado. Ele garante que apenas uma única instância vai poder ser criada para um objeto. Seu funcionamento é bastante simples:

  • A classe singleton é definida com um construtor privado (ou protegido se for o caso);
  • A classe singleton mantém uma referência privada e estática ao objeto unicamente instanciado; 
  • Um método público e estático é disponibilizado para que os objetos que acessarão o singleton possam recuperar sua instância.

Há vários problemas com esse design. Você vai encontrar várias publicações e discussões em newsgroups sobre isso. Mas vou me concentrar no problema da dificuldade de extensibilidade e de suporte a variações para a classe singleton.

Depois de implementado e largamente utilizado em toda a sua aplicação, o que acontece se o seu singleton precisar variar? Ou seja, hoje ele trabalha de uma forma, mas agora eu me deparo com um requisito de negócio que me obriga a variar a implementação do singleton em diferentes cenários de uso da minha aplicação. É nessa hora que você se arrepende com todas as forças de ter utilizado o singleton para projetar sua solução.

Isso aconteceu comigo recentemente quando precisei adequar minha aplicação para trabalhar com um cenário de negócio diferente. Eu desenvolvi aqui na empresa uma solução de instalação para o nosso sistema, o Phidelis. Depois de usar arquivos de instalação MSI durante muito tempo, percebemos que ele não tinha os facilitadores que precisávamos para fazer uma instalação (one-step) do nosso aplicativo. Partimos então para criar o nosso próprio aplicativo de instalação. Esse aplicativo possibilita a instalação do sistema simultaneamente em várias máquinas, considerando o uso de clusters e de módulos do sistema sendo executados em ambientes diferenciados. O instalador também gerencia a instalação do sistema em vários ambientes (produção, homologação, etc).

No projeto desse aplicativo eu criei uma classe Singleton chamada ListaDeModulos que me permitia ter acesso a lista de módulos do sistema que o aplicativo instalaria no ambiente de um cliente. O código é mais ou menos o seguinte:

    public class ListaDeModulos

    {

        List<Modulo> _modulos = new List<Modulo>();

        private static ListaDeModulos _instance;

        private ListaDeModulos()

        {

            _modulos.Add(new IntranetAdministrativa());

            _modulos.Add(new PortalDoAluno());

            _modulos.Add(new PortalDoProfessor());

            _modulos.Add(new CobrancaExterna());

            _modulos.Add(new Servicos());
            
_modulos.Add(new VestibularOnLine());

   }

 

        public static ListaDeModulos GetInstance()

        {

            if (_instance == null)

                _instance = new ListaDeModulos();

 

            return _instance;

        }

       

        public List<Modulo> All

        {

            get { return _modulos; }

        }

 

        public Modulo WithId(string id)

        {

            foreach (Modulo each in _modulos)

            {

                if (each.Id.ToLower() == id.ToLower())

                    return each;

            }

           

            return null;

        }

   

No início parecia um bom design. O princípio Open-closed parecia estar atendido, já que a classe estava aberta para receber novos módulos ou para a remoção de módulos que ficassem depreciados. Bastaria para isso, criar uma nova implementação para a classe abstrata Modulo e adiciona-la à lista gerenciada pelo singleton.

O problema de design surgiu quando um novo requisito de negócio foi proposto. A empresa agora está trabalhando em uma simplificação do sistema para atingir clientes pequenos a um custo mais baixo. Esta nova versão do sistema chama-se Phidelis Express e terá uma lista de módulos completamente diferente da lista de módulos oferecida para clientes corporativos.

BAD DESIGN!

Meu projeto não atende a esse cenário. Eu tenho agora dois instaladores e a lista de módulos estará variando quando eu usar o instalador para o sistema Corp e para o sistema Express. Só que minha lista de módulos é rígida, e pior, ela é pública e acessada em várias partes do código com uma única instrução que agora deveria estar variando conforme o tipo de instalação que estiver ocorrendo:

ListaDeModulos.GetInstance().All;

Estou agora sentindo as mazelas do singleton na carne. A melhor solução agora será refatorar todo o projeto removendo o singleton e tratando a ListaDeModulos como uma abstração que pode ser implementada de formas diferentes em cenários alternativos. Muito trabalho para extender uma funcionalidade que poderia ser muito mais facilmente resolvida se o singleton não fizesse parte do projeto.

         Moral da história... Pense bem da próxima vez que precisar recorrer ao Singleton para resolver um problema de design. Você pode estar retirando toda a flexibilidade do seu projeto e dificultando a evolução do seu sistema.

Tags:

Coding | Design

Sobre o Autor

Alisson Vale Alisson Vale
Líder de Projeto da Phidelis Tecnologia.


E-mail me Send mail
      Sign in

Últimos posts

Últimos comentários

Termo de Responsabilidade

Este site apresenta apenas opiniões pessoais. Não necessariamente representa as opiniões ou práticas da Phidelis Tecnologia.

© 2008