Julho de 2016

Volume 31 - Número 7

Cutting Edge - Reflexão sobre o Code First, Persistência e Modelagem de Domínio

Por Dino Esposito | Julho de 2016

Dino EspositoCode First é um trecho de funcionalidade que você encontra no EF (Entity Framework) e que permite modelar tabelas de banco de dados usando classes .NET simples. Honestamente, acho que o nome Code First é um pouco nebuloso, mas o trabalho que ele realiza nos bastidores é extremamente claro. O Code First estabelece a estrutura do banco de dados que está sendo usado e fornece uma API abrangente orientada ao objeto para trabalhar com dados armazenados.

Apresentado no EF4.1, o Code First está incluído até o EF6 e é uma das abordagens que podem ser usadas para modelar seu banco de dados em classes C# ou Visual Basic. Até o EF6, também é possível usar um designer do Visual Studio para inferir o esquema do banco de dados, salvá-lo em um arquivo XML com a extensão EDMX e criar classes ad hoc para usar no código. O designer do Visual Studio também permite que você crie um modelo abstrato que será usado mais tarde para criar um banco de dados físico.

Resumindo, até o EF6 havia duas maneiras de fazer a mesma coisa, mas a abordagem do EDMX, embora seja funcional, é mais problemática do que a outra. Por esse motivo, o próximo EF7 descontinuará o suporte ao EDMX.

Com o passar dos anos, o Code First foi associado ao DDD (Domain-Driven Design) e talvez isso tenha contribuído para a ideia geral de que o Code First e o EDMX não são exatamente duas maneiras de fazer a mesma coisa. Nesta coluna, oferecerei uma perspectiva mais arquitetônica do Code First e traçarei uma linha entre o território do modelo de domínio e o território do modelo de persistência. O Code First, junto com o LINQ, realiza um sonho antigo da maioria dos desenvolvedores: Esconde as complexidades do acesso aos dados (tabelas, índices, restrições) atrás de uma fachada orientada ao objeto e é qualificado como essa linguagem inédita de definição de dados orientada ao objeto.

Histórico

Ao trabalhar com bancos de dados relacionais, você atua seguindo regras da linguagem SQL. Enquanto codifica aplicativos, você age de acordo com as regras da linguagem de programação de sua escolha. Assim, é necessário ter uma camada de abstração para ligar a natureza orientada ao objeto, ou processual, das linguagens de programação de nível superior à linguagem SQL. No Microsoft .NET Framework, essa camada de abstração é a estrutura ADO.NET.

ADO.NET é uma camada de abstração relativamente fina no sentido de que apenas fornece objetos para seu código .NET colocar comandos SQL. O ADO.NET não mapeia dados enviados ou recuperados do banco de dados às estruturas de dados orientadas ao objeto ad hoc. No ADO.NET, as ferramentas para acessar os dados são totalmente mescladas ao ambiente do .NET Framework, mas os dados são simples.

Há cerca de uma década, a estrutura O/RM (Objeto/Mapeador Relacional) surgiu no horizonte. Uma estrutura O/RM mapeia as propriedades de uma classe para as colunas de uma tabela. Ao fazer isso, ela implementa um monte de padrões de design, como o Mapeador de Dados, Unidade de Trabalho e Objeto de Consulta. Uma estrutura O/RM também mantém internamente um conjunto de regras de mapeamento e informações sobre o esquema do banco de dados de destino. Essas são informações concretas e tangíveis que precisam ser salvas em algum lugar, de alguma maneira. NHibernate, o primeiro O/RM que já existiu no espaço .NET, armazena essas informações em um arquivo XML. Inicialmente, o EF usou a mesma abordagem com arquivos EDMX e acrescentou um bom designer para gerenciá-los no Visual Studio. O Code First mapeia as propriedades de classe para colunas e tabelas por meio de atributos ou de uma API fluente (e mais avançada).

Em uma postagem de blog que surgiu há alguns meses, a equipe do EF explicou claramente a motivação para tornar o Code First a única forma compatível para armazenar modelos de dados no EF7. (Leia o texto na íntegra em bit.ly/1sLM3Ur.) Na postagem, a expressão “code-based modeling” (modelagem baseada em código) é usada como um nome mais explicativo da atividade real do Code First. Eu concordo totalmente.

DDD em poucas palavras

DDD é uma abordagem ao desenvolvimento de software pensada originalmente como um conjunto de regras aplicado sistematicamente ao controle de um nível monumental de complexidade (ou seja, uma quantidade imensa de regras e entidades de negócio). Embora o DDD seja a estrela em sistemas de porte muito grande, com pelo menos centenas de regras e entidades, ele tem muito valor para desenvolvedores e arquitetos em cenários mais simples. Resumindo, não há motivos para não aplicar certas partes do DDD em praticamente qualquer projeto de software. A parte do DDD que vale a pena em qualquer projeto é o Design Estratégico e é centrada na aplicação de alguns métodos bem conhecidos: Linguagem Ubíqua, Contexto Associado e Mapa de Contexto. Esses padrões analíticos tem pouca associação às classes e tabelas de banco de dados reais que você acaba usando no aplicativo final, embora o objetivo final de usá-los seja escrever um código mais efetivo. Os padrões de design estratégico do DDD têm como meta analisar o domínio de negócio e visualizar a arquitetura de nível superior do sistema resultante. A Figura 1 fornece uma possível arquitetura de nível superior para uma solução de comércio eletrônico. Cada bloco representa um contexto associado identificado durante a análise e apresentado para acelerar o desenvolvimento.

Exemplo de Arquitetura de Nível Superior com Contextos Associados
Figura 1 Exemplo de arquitetura de nível superior com contextos associados

Cada contexto associado que surge da análise tem sua própria linguagem comercial, sua própria arquitetura de software (incluindo tecnologias) e seu próprio conjunto de relações com outros contextos associados. Cada contexto associado pode ser implementado usando a arquitetura de software mais adequada à quantidade determinada e às habilidades das equipes envolvidas, às restrições de orçamento e tempo e às preocupações de qualquer outra parte interessada, por exemplo, licenças de software existentes, custos, experiência, políticas, etc. O DDD também fornece uma sugestão clara de qual poderia ser uma maneira realmente efetiva de compilar algo para um contexto associado: a arquitetura em camadas.

O Modelo de Domínio em uma Arquitetura em Camadas

A Figura 2 fornece as linhas gerais de uma arquitetura em camadas. Ela tem quatro camadas, que vão desde a apresentação até a infraestrutura, com uma camada de aplicativo e uma camada de domínio no meio. Resumindo, é uma forma generalizada da famosa arquitetura de três camadas (apresentação, negócio, dados) com uma separação perfeita entre a lógica de casos de uso que mudam com os casos de uso que você considera na apresentação e a lógica de domínio inerente à forma específica de fazer negócios, além disso é comum a todos os casos de uso e camadas de apresentação.

Esquema de uma Arquitetura em Camadas
Figura 2 Esquema de uma arquitetura em camadas

A camada de infraestrutura inclui o que for necessário para implementar e oferecer suporte aos casos de uso e para persistir o estado das entidades de domínio. A camada de infraestrutura, portanto, inclui componentes que mostram a cadeia de conexão para conectar-se ao banco de dados.

A noção de “modelo de domínio” é fundamental à abordagem DDD. É bem simples, um modelo de domínio é um modelo de software que você cria para representar totalmente o domínio do negócio. Colocando de outra forma, é qualquer coisa que você possa fazer com o software que permita a lidar com o domínio que você está vendo. Normalmente, um modelo de domínio é preenchido com entidades, eventos e objetos de valor, e algumas das entidades e objetos de valor funcionam juntas para formar uma unidade indissolúvel. O DDD chama isso de um “agregado” e há a raiz do agregado. A persistência ocorre no nível das raízes do agregado e a raiz do agregado normalmente é responsável pela persistência de todas as outras entidades e objetos de valor no agregado.

Como você codificaria um agregado de tipos de entidade e de valor? Depende do paradigma da programação que você estiver usando. Na maior parte do tempo, um modelo de domínio é um modelo orientado ao objeto em que entidades são classes com propriedades e métodos e objetos de valor são estruturas de dados imutáveis. O uso de uma linguagem funcional e estruturas de dados imutáveis é opcional. No entanto, pelo menos em certos tipos de domínios comerciais.

O Code First é uma tecnologia concreta estritamente relacionada ao desempenho das tarefas de acesso aos dados. O aspecto mais marcante do Code First é o uso de classes para representar o esquema subjacente de tabelas e os dados usados pelo aplicativo. Os dados usados pelo aplicativo são os mesmos que os dados persistidos pelo aplicativo por tabelas relacionais? Ou, colocado de outra forma, o conjunto de classes que o Code First usa para mapear tabelas no banco de dados relacional é o mesmo que o modelo de domínio do aplicativo? Bom, eu diria que não, mas quando o assunto é arquitetura de software, como sempre, depende.

O Modelo de Domínio em uma Arquitetura em Camadas

Às vezes, o Code First é associado ao DDD devido à sua habilidade de modelar dados do aplicativo pelas classes. Embora às vezes seja mais do que aceitável ter um conjunto único de classes que lida com a lógica de negócios do domínio e com questões de persistência, em termos gerais, o modelo de domínio e o modelo de persistência são distintos. O modelo de domínio é o modelo de software que você usa para expressar a lógica de domínio do sistema e implementar suas regras de negócio. Pode ser um modelo orientado ao objeto e, também, um modelo funcional ou até mesmo uma coleção simples de métodos estáticos expostos a partir de classes auxiliares.

O lance do DDD é que você mantém as questões de persistência fora do modelo de domínio e, no design do modelo de domínio, você se concentra mais naquilo que uma entidade de negócios faz (e como ela é usada) do que nos dados que ela contém e gerencia. Uma abordagem centrada no comportamento quebra um nível enorme de complexidade a ponto de poder ser tratado de forma efetiva com o código. Vamos considerar um exemplo simples, um jogo, como mostra a Figura 3.

Comportamento versus Dados na Entidade de um Modelo de Domínio
Figura 3 Comportamento versus Dados na Entidade de um Modelo de Domínio

Para expressar o comportamento de uma entidade de partida no contexto de um sistema de pontuação, você modelaria ações como Início, Fim, Objetivo, Intervalo e o que mais fizer sentido na situação específica. Esses métodos implementam todas as regras de negócio e garantem que apenas ações consistentes com o estado atual da entidade sejam executadas programaticamente. Por exemplo, o método Goal seria disparado se fosse invocado em uma instância Match atualmente suspensa devido a um intervalo. O estado interno da entidade Match contém todas as propriedades que você associaria normalmente a uma entidade como essa em um modelo relacional puro, exceto se essas propriedades fossem somente leitura e atualizadas apenas internamente por meio de métodos.

Nem todas as classes que você possa ter em um modelo de domínio precisam ser persistidas e a persistência pode incluir todas as propriedades ou apenas algumas. Portanto, o Code First não se trata de modelagem geral de domínio, mas a API que mapeia propriedades para colunas de tabela pode ser usada para persistir as classes no modelo de domínio que precisam ser persistidas. Dessa forma, você tem um único modelo para o domínio que abrange as necessidades do negócio e de persistência.

O Problema dos Setters Privados

Na perspectiva da modelagem de domínio, você trabalha apenas com fluxos de trabalho de negócios que seguem uma entidade, conforme descrito por especialistas em domínio. Analisando novamente o exemplo de pontuação da partida, talvez ele não seja consistente com as regras de negócio que definem o estado da partida ou a pontuação de forma programática. Estado e pontuação, na verdade, mudam à medida que o fluxo de trabalho progride. Da mesma forma, você não terá um construtor padrão sem parâmetros, pois ele retornaria uma entidade Match sem algumas informações fundamentais, como os nomes das equipes e uma ID que vincularia de forma razoável a partida a uma competição. Ainda assim, se você estiver usando um único modelo para negócio e persistência, será necessário ter um construtor sem parâmetro. Caso contrário, o EF não seria capaz de retornar uma instância do tipo após uma consulta.

Mas há mais coisas a serem consideradas. Quando o EF executa uma consulta e retorna uma instância da classe Match, ele precisa acessar os setters de todas as propriedades para salvar na instância retornada um estado coerente às informações contidas no banco de dados. Essa exigência legítima do EF entra em conflito com as regras de design de um modelo de domínio. Em linhas gerais, é necessário existir uma maneira de forçar um estado em uma entidade de um modelo de domínio e, na maior parte do tempo, ela deve ser interna e não publicamente disponível por um código fora da camada. Essa é uma das finalidades dos serviços de domínio que, junto com o domínio, formam a Camada de Domínio da Figura 2. Se você usar o Code First, poderá conseguir o mesmo simplesmente marcando os setters como não públicos (internos, protegidos ou até privados) e adicionando um construtor padrão com a mesma visibilidade não pública. O EF ainda encontrará uma forma (por reflexão) de acessar membros privados e forçar um estado, mas os clientes públicos da API do domínio não o farão. Bem, não até que eles mesmo usem a reflexão.

Conclusão

Não é incomum navegar pela Web e encontrar artigos que colocam o Code First em um relacionamento com o DDD. O Code First se resume a persistência de um modelo orientado a objeto mapeado explicitamente em um conjunto de tabelas. Falando de forma conceitual, o modelo de domínio é outra coisa completamente diferente e até fica em uma camada diferente. No entanto, devido a alguns recursos específicos da API do Code First, por exemplo, lidar com setters e construtores privados, em alguns casos é possível usar um único modelo orientado ao objeto que inclua regras de comportamento e de negócio e possa ser persistido com facilidade para um banco de dados relacional.


Dino Espositoé o autor de “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) e de “Modern Web Applications” (Microsoft Press, 2016). Evangelista técnico das plataformas .NET e Android no JetBrains e palestrante frequente em eventos do setor no mundo todo, Esposito compartilha sua visão de software em software2cents@wordpress.com e, no Twitter, em @despos.

Agradecemos ao seguinte especialista técnico da Microsoft pela revisão deste artigo: Jon Arne Saeteras