Marco Baccaro

MCP • MCT • MCAD • MCSD • MCTS • MCPD Enterprise App Dev

Team System com metodologias ágeis

Olá a todos!
Para quem me conhece sabe que Team System é um produto no qual admiro bastante, bem como boas práticas de desenvolvimento de software e agilidade, segue abaixo o compartilhamento de alguns conteúdos bem interessantes.

Tell, don’t ask

Quando uma pessoa começa a ter contato com desenvolvimento de software orientado a objetos, ele certamente se depara com os termos: encapsulamento, herança e polimorfismo. Agora, porque mesmo depois de tanto tempo utilizando linguagens orientas a objetos, fracaçamos no implementação do encapsulamento? Encapsular é somente implementar atributos do tipo propriedades com get set? Alec Sharp há muitos anos fez a seguinte colocação: “Código procedural pegam as informações e tomam as decisões. Código orientada a objetos dizem para objetos fazer coisas”. Fato.

Precisamos ter em mente que devemos nos esforçar a dizer aos objetos que desejamos, não fazer-lhes perguntas sobre seu estado, tomar uma decisão, e em seguida dizer-lhes o que fazer, isso é o que o termo Tell, don’t ask quer nos dizer. Veja uma situação, dentre muitas que passa despercebido pela maioria dos desenvolvedores.


while (itens.HasMoreElements) {
  string name = itens.NextElement();
}

Analizando o código acima, se o atributo HasMoreElements é igual a true, então a tentativa de obter o próximo elemento da coleção deve funcionar. Agora, se esse código acima estiver sendo executado em um ambiente multi-thread, sem o uso correto de sincronização (lock), facilmente a invariante HasMoreElements não se sustente, de modo que alguma outra thread capiturou o último elemento antes da sua thread. Onde erramos nesse exemplo? Simplesmente não deveria ser utilizado uma informação do estado da coleção para tomar uma ação, mas sim deixá-la se gerenciar a si mesma. De certa forma quebramos o seu encapsulamento. Vamos para mais um exemplo, que de forma semelhante pecamos no dia-a-dia.

PedidoDTO pedido = new PedidoVendaDTO();
ProdutoDTO item = new ProdutoDTO() { Descricao = "AEIOU", ValorUnitario = 10.00, Qtde = 1 };
pedido.Carrinho.Add(item);

É muito comum se ver a utilização direta de uma propriedade do tipo coleção ser utilizada do lado de fora. O que acontece com esse exemplo, se preciso atualizar o total do pedido para alimentar uma outra propriedade chamada TotalPedido? Ou, realizar a reserva do item no banco de dados para essa transação? Complicou… pois o objeto PedidoVendaDTO está sendo manipulado externamente, em outras palavras, invadido. Quando deveríamos fazer algo do tipo:

PedidoDTO pedido = new PedidoVendaDTO();
ProdutoDTO item = new ProdutoDTO() { Descricao = "AEIOU", ValorUnitario = 10.00, Qtde = 1 };
pedido.AdicionarItem(item);
pedido.RemoverItem(item);
pedido.Carrinho; // Propriedade ReadOnly.

Objetos devem falar uns com os outros através de métodos. Quando eles começam a se comunicar por meio de atributos (propriedades) é indício que as coisas podem não estar indo bem. Em Domain-Driven Design o conceito de Agregados faz muito sentido, pois quem deve gerenciar o ciclo de vida dos atributos agregados é a entidade raiz. É comum encontrarmos a colocação que orientação a objeto é um modo de expressar entidades do mundo real dentro do software. Mas no mundo real quando vamos comprar um lanche, via de regra entramos em uma fila, solicitamos o nosso pedido, pagamos e aguardamos a entrega. Mas parecem que nossos objetos se comportam como se, cortamos a fila, fazemos o pedido e efetuamos o pagamento, e depois vamos no cozinha pegamos o lanche e vamos embora. Até a próxima.

Aggregates

Olá, todo mundo. Já faz um certo tempo que, na medida do possível, venho postando sobre Domain-Driven Design. Assunto esse em pauta faz uns bons anos, e com grande adoção e bons resultados. Dando sequência no assunto, hoje escrevo sobre uma das práticas utilizadas no DDD em busca de uma boa organização e compreensão do domínio, para chave no contexto de DDD.

Indo diretamente ao ponto, quando trabalhamos com nossos objetos, criando, processando, reconstruindo (tirando do relacional para a mémória)… Como saber onde um objeto formado por outros objetos começa ou termina? Em sistemas de armazenamentos, bem como banco de dados relacionais, temos diversas formas de travamento em relação ao limites. Pois para uma transação teremos chaves e dependências que nos guiaram no começo e término das tabelas. E nos objetos? Quem direciona os limites e o ciclo de vida de uma informação? Na relação de uma entidade com seu atributos tem que ser claro e transparente. Ex: Se excluirmos uma entidade Pessoa com nome, data de nascimento, sexo… devemos apagar o endereço também? Será que ninguém mais tem referência a esse endereço?

Em um modelo dirigido por domínio, utilizamos o conceito de Agregado , que é um grupo de objetos associados que tratamos como sendo uma unidade para fins de alterações de dados, de forma que cada agregado possui um entidade raiz e um limite (o que está dentro do agregado). Os agregados demarcam o escopo dentro do qual as invariantes (invariante: algo que não tem variação), tem que ser mantidas em cada estágio do ciclo de vida.

Exemplo:
Uma loja de peças automotiva que utiliza o modelo de um carro. O carro é uma entidade com uma identificação global (id), queremos distinguir esse carro de todos os outros carros do mundo. É possível que queiramos rastrear o histórico de rotação dos pneus nas quatro rodas. Para saber qual pneu é qual, eles também devem ser entidades com seus respectivos códigos identificadores. Mas é bastante improvável que nos preocupamos com a identidade desses pneus fora do contexto daquele carro específico. Se substituirmos os pneus e enviarmos para uma pilha anônima de descarte e reciclagem, o sistema não precisará mais rastreá-lo. O que acontece nesse caso é que o carro é a entidade raiz do agregado, cujo o limite também inclui os pneus.

Veja o diagrama ilustrativo direto do livro do Evans do exemplo citado acima:

Certamente precisamos de uma boa abstração e compreensão do negócio por parte do modelador para um entendimento correto das entidades raízes (roots) e seus objetos agregados. Não existe um cookbook para uma boa modelagem, mas abaixo estão algumas práticas de utilização de agregados:

  • A entidade raiz deve possuir uma identificação global e é em última instância responsável por verificar invariâncias.
  • As entidades dentro do limite possuem uma identificação local e única somente dentro do agregado.
  • Nada fora do limite do agregado pode fazer referência a qualquer coisa dentro, exceto à entidade raiz. A entidade raiz pod fornecer referência às entidades internas para outros objetos, mas esses objetos podem utilizá-las apenas de forma transitória, sem reter a referência.
  • Somente raízes dos agregados podem ser obtidas diretamente através de consultas ao banco de dados. Todos os outros objetos devem ser achados através de travessia de associações.
  • Uma operação de exclusão deve remover tudo dentro do limite do agregado de uma só vez.

Agrupe as Entidades e Objetos de Valor em agregados e defina os limites em torno do cada um. Escolhe uma Entidade para ser a raiz de cada agregado e controle todo o acesso aos objetos dentro do limite (escopo) através da raiz. Permita que objetos externos façam referência somente raiz, por exemplo: Não permitir utilizar o objeto Endereco diretamente sem a utilizar a entidade raiz Pessoa).

Padrões como Fabrics e Repositories nos ajudam a operar sobre os agregados, encapsulando a complexidade de transições específicas do ciclo de vida. De forma curta e objetiva, Agregado é um termo usado para definição das propriedades do objeto e o limites entre os objetos e seus relacionamentos. Boa modelagem e até o próximo post. Hugs.

Fechamento de Operações

É correto dizer que computação é da área da exatas. Embora muitas vezes de forma trágica ou cômica parece não ser (algumas vezes o 1 + 1 não retorna 2), isso devido a problemas de ambiente, bugs, intervenção humana e N outros fatores. Esse comentário foi apenas nos introduzirmos aos assunto da exatas (matemática). Quando efetuamos a multiplicação entre dois números reais (números racionais e irracionais), obtemos um outro número real. Como isso sempre é verdade, dizemos que os números reais são “fechados sob a operação de multiplicação”. Baseado nessa condição temos o conceito chamado FECHAMENTO DE OPERAÇÕES. Esse conceito traz uma enorme simplificação e interpretação de uma operação, de modo que recebendo um tipo de argumento o retorno é do mesmo tipo. Por exemplo, o uso de XSLT é transformar um documento XML em outro documento XML.Sempre que possível, defina uma operação que o tipo de retorno seja do mesmo tipo do(s) argumento(s). Se o implementador tiver um estado que é utilizado na computação, ele é efetivamente um argumento da operação. Por isso, o(s) argumento(s) e o valor de retorno devm ser do mesmo tipo que o implementador. Uma operação como essa é fechada sob o conjunto de instância daquele tipo. Uma operação fechada proporciona uma interface avançada sem introduzir qualquer dependência de outros conceitos, logo, bem objetiva e coesa.Esse padrão é mais utilizado com OBJETOS DE VALOR, do que com ENTIDADES, já que ENTIDADES tem um ciclo de vida importante no DOMINIO e não se pode simplesmente criar-se um novo. Podemos ter algumas exceções, mas de no geral ENTIDADES não são resultados de uma computação. Quanto mais procuramos reduzir interdependências e aumentar a coesão, mais próximos estamos desse padrão. Uma situação válida na fechamento de operações é o retorno de um tipo abstrato, por exemplo: argumento Funcionário e retorno Pessoa. O mesmo se aplica em um retorno de um objeto básico do framework, que elimina qualquer dúvida da mente de quem utiliza.

Exemplo de uma operação fechada:

class Program {

        static void Main(string[] args) {

            // Salarios fixos de exemplo.
            List<double> salaries = new List<double>() {
                100, 900, 5000, 9000, 490, 700, 3000, 270, 300
            };

            List<double> lowSalaries = GetLowSalary(salaries);

        }

        static List<double> GetLowSalary(List<double> salaries) {

            List<double> lows = new List<double>();

            salaries.ForEach(delegate(double salary) {

                if (salary <= 500) {
                    lows.Add(salary);
                }

            });

            return lows;

        }

    }

Nesse exemplo acima, o método  GetLowSalary retorna e um subconjunto da coleção salário, sendo assim uma operação fechada, fechada para outras referências e fechada para complexidade e falta de entendimento. Claro que é um exemplo sem regra de negócio qualquer, mas poderia ser uma operação complexa com uma computação avançada, porém com alta coesão. Podemos ter em nossos objetos complexos operações fechadas, enriquecendo nossas interfaces com objetividade, bem como o .NET Framework faz, principalmente nas coleções nativas. Veja alguns exemplos:

 class Program {

        static void Main(string[] args) {

            // Exemplo de fechamento com classe abstrata.
            // Pois o tipo string[] é um Array.
            string[] itens = new string[5] { "A", "E", "I", "O", "U" };
            Array vetor = null;
            itens.CopyTo(vetor, 5);

            // Exemplo simples, objeto string que recebe string e 
            // que retorna uma string após a computação.
            string vogais = "@BCDE";
            vogais = vogais.Replace("@", "A");


        }

    }

Chegamos ao fim do post, que teve o intuito de trazer a frente o conceito matemático de fechamento de operações para o mundo da computação. Assunto esse abordado em DDD por Eric Evans. Pouco mais de cultura e entendimento para melhores práticas, e aprimoramento continuo na coesão no desenvolvimento de softwares. Até +.

Expression Linq

Já se faz bons anos desde a entrada da proposta de uma linguagem declarativa de consulta compilável, LINQ. As linguagens declarativas, o que são classificadas de linguagens de quarta geração, existe a bastante tempo. O SQL foi criado no início dos anos 70 pela IBM e padronizado como o padrão ANSI anos depois por um instituto americano. Nesse modelo declarativo, diferentemente do imperativo como C, C++, C#, nós não escrevemos como obter um objetivo, apenas declaramos nossas intenções. Por exemplo: “SELECT id, nome FROM TB_CLIENTE WHERE id > 10″. Nesse exemplo, não sabemos que tipo de algoritmo será executado, mas sabemos que atenderá nossa intenção, ou melhor, nossa declaração.Sou um grande admirador do LINQ, pois a idéia do Anders foi realmente boa, ter uma linguagem fortemente tipagem e com segurança contra erros banais em tempo de execução. Tendo essa linguagem, ficou até mais fácil e elegante acrescentar os provider para XML, Banco de Dados (ORM) entre outros, pois nada mais eram que uma espécie de repositório que uma linguagem de consulta declarativa estava pronta. Talvés para muitos, até aí nada de novo, apenas um pouco de cultura…rs. O que sempre fizemos foi implementar algoritmos para construir instruções declarativas em tempo de execução. Algumas de forma bem rudimentar permitindo (concatenação de string por exemplo) facilmente até ataque de SQL Injection. Nós temos essa construção em tempo de runtime porque muitas vezes dependemos das seleções ou entradas de dados do usuário final através de uma UI. Exemplo disso é qual o tipo de pesquisa para encontrar uma pessoa física, RG, CPF, parte do nome, sexo, idade, todos combinamos… e por aí vai.Para construir consultas em tempo de runtime com uma linguagem compilável como LINQ, precisamos utilizar as classes do namespace System.Linq.Expression. A Microsoft disponibilizou parte das classes que eles utilizam internamente para criar consultas e expressões lambda para nós. Com isso podemos montar filtros (condições where) entre outras em tempo de runtime e criar uma consulta LINQ dinamicamente.

Existe uma classe responsável para cada parte de uma expressão lógica. A tabela abaixo destaca as respectivas classes.

Expression Fornece um método estático para criar outros objetos de expressão.
BinaryExpression Representa uma expressão com uma operador binário. Ex: >, <, !=, == e etc.
ConstantExpression Representa uma referência constante em uma expressão. Ex: 50 de typeof(int), “Olá” de typeof(string) e etc.
MemberExpression Representa a referência de um membro (membro obtido por reflection). Ex: Propriedade qualquer de um objeto, x.Id > 10.
UnaryExpression Representa uma operação unária na expressão. Ex: Negativo de um valor.

A imagem abaixo ilustra a divisão lógica da expressão, no qual cada item é composto por uma classe.

Para ilustrar a utilização do consultas linq dinâmicas, criei um exemplo utilizando linq to object no qual a condição da filtragem (clausula where), será criado em tempo de execução. Para o exemplo criei uma entidade complex type chamado Student para o exemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ExpressionLinqSample {

   public class Student {

        public int Id { get; set; }
        public int Registry { get; set; }
        public string Name { get; set; }

    }

}

Nessa classe que denominei DynamicLinq, crio a expressão linq baseado nos parâmetros informados pelo usuário na UI:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace ExpressionLinqSample {

    public class DynamicLinq {

        static public Expression<Func> MakeExpression(int field, int paramFromUi) {

            Type type = typeof(Student);
            ParameterExpression paramExp = Expression.Parameter(type, "Student");
            MemberInfo studentyProperty = null;

            switch (field) {
                case 1:
                    studentyProperty = type.GetProperty("Id");
                    break;
                case 2:
                    studentyProperty = type.GetProperty("Registry");
                    break;
            }

            MemberExpression memberExp = Expression.MakeMemberAccess(paramExp, studentyProperty);
            ConstantExpression constExp = Expression.Constant(paramFromUi, typeof(int));
            BinaryExpression binExp = Expression.GreaterThanOrEqual(memberExp, constExp);
            return Expression<Func<Student, bool>>.Lambda<Func<Student, bool>>(binExp, paramExp);            

        }

    }

}

Esse é método Main de uma aplicação console para iniciar o programa e consumirmos o método de criação de consulta dinâmica. Veja que o expression delegate é criado a partir da consulta dinâmica criado. Como um delegate, ele executará o algoritmo confeccionado em runtime.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace ExpressionLinqSample {

    class Program {

        static void Main(string[] args) {

            List<Student> students = new List<Student>() {  
              new Student{Id = 10, Registry=100, Name="Paulo"},   
              new Student{Id = 20, Registry=200, Name="Pedro"},   
              new Student{Id = 50, Registry=300, Name="Felipe"},   
              new Student{Id = 60, Registry=400, Name="Lucas"} 
            };

            Expression<Func<Student, bool>> expression = DynamicLinq.MakeExpression(1, 50);      
            IEnumerable resultStudents = students.Where(expression.Compile());

            foreach (Student item in resultStudents) { 
               Console.WriteLine(item.Name);      
            }

        }

    }

}

Veja abaixo o conteúdo da instância do delegate chamado “expression” na janela Immediate do Visual Studio, nada mais que uma instrução lambda final:

De forma totalmente dinâmica criamos a condição linq “tal que Student.Id seja maior ou igual a 50″. E assim poderíamos ter criado inumeras outras condições que satisfaça uma regra de negócio, bem como formulário de pesquisa baseado nos filtros desejado pelo usuário. Até +.

Covariancia, Contravariancia e Invariancia

Caríssimos, provavelmente essas palavras do título do post não seja algo muito comum para você. Ao menos a chance é grande. Acho isso porque muitas vezes temos o entendimento e compreensão de algumas coisas, mas não sabemos o nome delas. Para iniciar, vamos dizer que os dados (nossos objetos), podem ser de três tipos, são eles:Covariância: Conversão entre tipos de dados efetuados somente com base na herança entre os tipos de dados, e que segue o sentido “Generalização”, do objeto especializado para o generalizado. Exemplo:

// Lembre-se que string herda de object no framework!
object instanceObject = new object();
string instanceString = "123 Testando";

//Cast implicito.
instanceObject = instanceString;

Contravariância: Conversão entre tipos de dados efetuada somente com base no sentido inverso da herança entre os tipos de dados. Nesse caso uma “Especialização”, do objeto generalizado para o especializado. Exemplo:

// Lembre-se que string herda de object no framework!
object instanceObject = new object();
string instanceString = "123 Testando";
//Cast explícito obrigatório.
instanceString = (object)instanceObject;

Invariância: Restrição da conversão entre tipos de dados. Exemplo:

List<object> objectList = new List<object>();
List<string> stringList = new List<string>();
objectList = (List<object>)stringList;

O framework identifica como um erro, isso por questão de segurança. Isso está correto? Sim. Quando lidamos com generics, um tipo List<object> é um tipo, como List<string> é outro, não existe vínculo de herança entre eles. Se tentar fazer um cast explícito conforme o exemplo abaixo, em design time passa, mas em tempo de runtime ocorrerá um erro InvalidCastException.

objectList = (List<object>)stringList;

No framework .Net, quando trabalhamos com generic as interfaces são invariantes por padrão, e as classes concretas que implementam um interface genérica sempre serão invariantes. Como no caso das interfaces genéricas é um comportamente padrão, podemos alterá-los, permitindo conversão entre interfaces. Para isso temos que utilizar os recursos in e out.

interface IStoreWrapper<T> {
   void SetData(T data);
} 

interface IRetrieveWrapper<T> {
   T GetData();
} 

class Wrapper<T> : IStoreWrapper<T>, IRetrieveWrapper<T> { 

  private T storedData; 

  void IStoreWrapper.SetData(T data) {
     this.storedData = data;
  } 

  T IRetrieveWrapper.GetData() {
     return this.storedData;
  } 

}

Wrapper<string> stringWrapper = new Wrapper<string>();
IStoreWrapper<string> storedStringWrapper = stringWrapper;
storedStringWrapper.SetData("Hello");
IRetrieveWrapper<string> retrievedStringWrapper = stringWrapper;

Esse exemplo acima funciona? Resposta: Não. O motivo é o mesmo citado no início, segurança. Para resolver o esse problema, façamos a seguinte modificação:

interface IRetrieveWrapper<out T> {
   T GetData();
}

Em situações como esta, onde o tipo de parâmetro é apenas de retorno (get), você pode informar ao compilador que alguns conversions implícitas são legais e não restringir. Você faz isso especificando a palavra chave out. Em caso contrário, onde queremos apenas o set no métodos das interfaces genéricas, no lugar o out colocamos o in. Um exemplo disso é o contrato IComparable, que precisa receber dois tipos impostos na declaração do generics para compará-los. Exemplo:

public interface IComparer<in T> {
   int Compare(T x, T y);
}

Na prática:

Object x = ...;
Object y = ...;
ObjectComparer<object> comparer = new ObjectComparer<object>();
IComparer<object> objectComparator = objectComparer;
int result = objectComparator(x, y);
IComparer<object> stringComparator = objectComparer;

Sei que essa história toda pode parecer confusa, mas é assim que acontece na prática e tudo isso faz sentido. Porém, muitas vezes nos deparamos com erros de conversões e não imediato não compreendemos. Tendo esse entendimento em mente irá nos auxiliar a compreender melhor os erros de conversões no dia-a-dia. Hugs!

CreateInstanceAndUnwrap

Olá todo mundo! Faz um certo tempo que fiz um post falando do pattern plugin Aqui. Nele eu falo do conceito e aplicabilidade, no qual inclusive cito uma necessidade de negócio real que tive. Esse post fala do método CreateInstanceAndUnwrap, que de forma extremamente simples, ou melhor dizendo, uma linha de código, efetuar a instância de uma biblioteca (dll) para consumo da mesma, através de contrato de interface sem a necessidade de inclusão de referência ao projeto.

Esse método nos permite trabalhar o conceito do plugin pattern, sem utilizar mecanismos avançados ou refletion. Basta informarmos o nome do assembly existente na pasta do projeto e mesmo não havendo referencia do mesmo o método CreateInstanceAndUnwrap se encarregar do serviço.

Para ilustrar a simplicidade do uso montei uma solução com os seguintes projetos: CreateInstanceAndUnwrap project como o projeto main para a execução do exemplo, NotificationContract project para um assembly exclusivo para a interface de contrato e por último o NotificationImplements que faz referência ao NotificationContract e implementa a interface de contrato. Veja abaixo a imagem da solução:

Aqui temos a construção da interface para notificação, dentro do projeto NotificationContract.

namespace NotificationContract {

    public interface INotification {
        void Notify(string message);
    }

}

Agora no projeto NotificationImplements, adiciono a referência ao projeto NotificationContract, crio a classe concreta para notificação implementando a interface de contrato INotification.

namespace NotificationImplements {

    public class Notification : INotification {

        public void Notify(string message) {
            Console.WriteLine(message);
        }

    }

}

Por último, temos o projeto CreateInstanceAndUnwrap para execução do teste, no qual tem referência apenas ao projeto da interface de contrato. O código abaixo vai instanciar e invocar o método da classe concreta, sem referência de projeto, o que podemos dizer, “plugado”.

public class Program {

    static void Main(string[] args) {

        string assemblyName = "NotificationImplements, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
        string typeName = "NotificationImplements.Notification";
        INotification notification = (INotification)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
        notification.Notify("Call method less internal reference.");

    }

}

A idéia é válida, a necessidade de plugar componentes muitas vezes existe e a solução da aplicabilidade é simples e rápido. Esse modo de resolver é através do framework. Net por si só. Temos outros meios, tal como MEF, que nos trás features adicionais e possibilidade de utilização de técnicas interessantes como Service Locator e Dependency Injection. Fica aí a idéia, até o próximo post.

Specification Pattern

Quando modelando um domínio de um software, com base na extração das informações e conhecimento do negócio, estamos identificando os objetos (entidades, objetos de valor, agregados e afins…) que encapsulam procedimentos com seus objetivos e intenções. Mas existem situações que nessa modelagem identificamos processos que interage com objetos, mas nos torna difícil compreender de que entidade, por exemplo, tal procedimento pertence. Esse procedimento pode muitas vezes ser utilizado em varíos contextos do sistema, é ser parte da linguagem onipresente, e para evitar duplicidade de um procedimento, acabamos elegendo um objeto por conter tal procedimento. Com isso, criamos objetos com design estranho!

Quando existe mais de uma forma de executar um processo, uma abordagem é tornar o próprio algoritmo parte essencial dele, um objeto por si só. A chave para distinguir um processo que deve se tornar explícito de um processo que se deve permanecer oculo é simples: “Será que ele é algo sobre o qual os especialistas do domínio falam, ou será que ele é simplesmente parte do mecanismo do software?”

Foi em situações como essa que nasceu o pattern Specification, que fornece uma maneira concisa de expressar determinados tipos de regra, separando-as da lógica condicional e tornando-as explícitas pra o domínio, que é o coração vital de um software (DDD). O conceito básico do pattern Specification é bastante simples e nos ajudar a pensar sobre um problema encontrado na modelagem de domínios: “As regras de negócio geralmente não se enquadram na responsabilidade de nenhuma ENTIDADE ou OBJETO DE VALOR óbvio e sua variedade de combinações podem sobrecarregar o significado básico do objeto de domínio”. Retirar essas regras da camada de domínio é ainda pior, pois o código do domínio passa a não expressar o modelo.

Com o conceito criamos objetos de regras separados e combináveis, que podemos chamá-los de “predicados”. Colocando de outra forma, esse novo objeto é um Specification. Um Specification declara um restrição sobre o estado de outro objeto, que pode ou não estar presente, sendo utilizado para verificar se ele satisfaz os critérios especificados. Specification são normalmente testes simples com propósitos especiais, como por exemplo, “Validar se um cliente é inadimplente”. Em casos com regras complexas, o conceito pode ser ampliado para permitir combinações de especificações simples, assim como os predicados são combinados com operadores lógicos.

Aplicando o Specification

Uma grande particularidade o Specification é que ele pode unificar funcionalidades diferentes dentro do aplicativo. Talvéz seja necessário especificar o estado de um objeto para um ou mais dos três propósitos:

1- Validar um objeto para verificar se ele preenche alguma necessidade ou está pronto paa algum propósito.
2- Selecionar um objeto a partir de uma coleção (Busca em banco de dados).
3- Especificar a criação de um novo objeto para atender alguma necessidade, (Combinado com Fábricas).

Validação:
A utilização mais simples de um specification é a validação, é o uso que demonstra o conceito de forma mais direta. Exemplo: um sistema de e-commerce que precisa analisar um algoritmo que levanta uma bandeira vermelha, caso o cliente seja inadimplante.

Seleção:
A seleção de um subconjunto de uma coleção com base em alguns critérios. Exemplo: Como no exemplo acima, só que precisamos selecionar os clientes inadimplantes dentro de um repositório. Nesse caso é vincularmos uma Specification de validação dentro de um Repository, fazendo um filtro de negócio na consulta de objetos.

Abaixo, ilustro um exemplo prático do Specification Pattern. Nesse cenário, uma empresa faz o carregamento e transporte de cargas. Para isso, utiliza-se containers, no qual tem especialidades, tais como: container blindado, normal ou ventilado. Pois dependendo do produto embalado dentro de barris, serão alocados dentro desse containers para o transporte. Mas um produto químico precisa de ventilação, bem como um explosivo precisa estar em um container blindado, e assim por diante. Vejam o diagrama de classe desse nosso caso de exemplo:

Baseado no diagrama de classe acima, criei um exemplo prático em Java como ilustração em um caso prático.
Criação do classe Container:

public class Container {
    
    private List contents;
    private EnumSet features;
    
    public List GetContents() {
        return contents;
    }
   
    public Set GetFeatures() {
         return features;      
    }
    
    public Container() {
        features = EnumSet.noneOf(ContainerFeatures.class);
        contents = new ArrayList();
    }   
     
    public boolean IsSafetyPacked() {
        
        Iterator iterator = GetContents().iterator();
        
        while(iterator.hasNext()) {
            
            Drum drum = iterator.next();
            
            if (!drum.GetProduct().GetSpecification().IsSatisfiedBy(this)) {
                return false;
            }
            
        }
        
        return true;
        
    }

}

Classe ContainerSpecification (Specification Pattern):

public class ContainerSpecification {
    
    private ContainerFeatures requiredFeature;
    
    public ContainerSpecification(ContainerFeatures feature) {
       requiredFeature = feature;
    }
    
    public boolean IsSatisfiedBy(Container aContainer) {
       return aContainer.GetFeatures().contains(requiredFeature);  
    }

}

Enumerador para os tipos de containers:

public enum ContainerFeatures {    
    AMORED,
    VENTILATED,
    NORMAL
}

Classe Drum (Barril que inseridos nos containers):

public class Drum {
    
    private Product product;
     
    public Product GetProduct() {
        return product;
    }

    public void SetProduct(Product product) {
        this.product = product;
    }

}

Classe abstrata Product (Que são adicionados em barris e que tem característica de armazenamento):

public abstract class Product {
    
    private ContainerSpecification specification;
    
    public ContainerSpecification GetSpecification() {
        return specification;
    }

    public void SetSpecification(ContainerSpecification specification) {
        this.specification = specification;
    }  
       
}

// Produto especilizado explosivo
public class Tnt extends Product {      
    
}

Executando na prática:

public class Samples {

    public static void main(String[] args) {
        RunSpecificationPattern();
    }
    
    public static void RunSpecificationPattern() {
        
        // Criação de container com especificação do tipo blindado (ARMORED).
        Container container = new Container();
        container.GetFeatures().add(ContainerFeatures.AMORED);
        
        // Criação de produto TNT com especificação de 
        // armazenamento em container blindado (ARMORED).
        Tnt product = new Tnt();
        product.SetSpecification(new ContainerSpecification(ContainerFeatures.AMORED));
        
        // Criação de uma barril de armazenamento com inclusão do produto TNT.
        Drum drum = new Drum();
        drum.SetProduct(product);
        
        // Adição do barril ao container.
        container.GetContents().add(drum);      

        if (!container.IsSafetyPacked()) {
            System.out.println("Packed failed!");            
        }
        
    }
    
}

Bom, espero ter conseguido de forma resumida apresentar e expressar a boa utilidade desse padrão, que é uma técnica para manter regras dentro do domínio, sem poluir os objetos, dando a eles responsabilidades muitas vezes a sobrecarregar, porque precisamos achar um “dono” para o um processo. Eu particularmente me deparei muitas vezes com essa situação enquando modelava (… e ainda modelo!) domínios de software. Até o próximo post!

WS-BPEL

Se tem uma sigla que realmente tem força no mercado atual é, SOA. Muitas outras siglas e modelo de negócios derivam do modelo e estilo de arquitetura SOA. Os padrões de integração de sistemas estão cada vez mais sofisticados e os middlewares para enriquecimento e alto nível de serviços estão de vento em polpa. Hoje quero falar de um dos recursos que está presente em praticamente todos os middlewares de SOA, ele se chama BPEL.

A linguagem WS-BPEL (Web Services Business Process Execution Language) é um padrão criado pela OASIS para a execução de processos de negócios, descrevendo como ocorre o relacionamento entre diversos web services participantes de uma composição simples ou complexa. A programação com WS-BPEL é parecida com a programação das linguagens de programação já existentes, na medida me que ela oferece determinados tipos de construções como estruturas de repetição, condicionais, variáveis e atribuições. Este fato possibilita que o processo de negócio seja visto como um algoritmo. O uso desses recursos é simples e limitado, objetivando a facilidade de uso e aprendizagem da mesma.

Uma composição de serviços especificada em WS-BPEL é vista como serviço, ou seja, quem usou um processo de negócio definido em WS-BPEL não tem a ciência que na verdade está usando vários serviços para realizar uma determinada operação. Por ser um web service, a composição necessita ter sua interface baseada em WSDL, troca de mensagens com outros web services através do protocolo SOAP e pode ser cadastrado em um repositório UDDI para fins de consulta. O padrão WS-BPEL assume que os serviços participantes da composição estejam descritos usando WSDL.

Um processo BPEL pode ser síncrono ou assíncrono. Um processo BPEL síncrono bloqueia o cliente (aquele que está usando o processo) até que o processo termine e retorne um resultado para o cliente. Um processo assíncrono não bloqueia o cliente. Em vez disso, ele usa um retorno de chamada para retornar o resultado (se houver). Geralmente, os processos assíncronos são usados para processos de longa duração, e os síncronos são usados para processos que retornam um resultado em um tempo relativamente curto.
As funcionalidades mais importantes de WS-BPEL são listadas a seguir:

• Descrever a lógica dos processos de negócio através da composição de serviços;
• Tratamento de operações de modo síncrono ou assíncrono;
• Invocar serviços de maneira seqüencial ou paralela.
• Prover mecanismos para tratar erros durante a invocação de serviços;
• Manter atividades de longa duração (transacionais), assim como interrompe-las;
• Reiniciar uma composição que foi interrompida ou apresentou erros;
• Agendar a execução de atividades e definir a ordem em que elas irão executar.

Conceitos do BPEL

Existem alguns elementos fundamentais da linguagem WS-BPEL que podem ser utilizados para a especificação de processos de negócio. A estrutura geral de um Processo de Negócio especificado em WS-BPEL é formada por quatro seções: PartnerLinks, Variables, FaultHandlers e Activities.

PartnerLinks: seção que define os diferentes parceiros (partes envolvidas) que interagem com o Processo de Negócio durante toda sua execução. Eles são usados para identificar a funcionalidade que deve ser oferecida por cada serviço parceiro. As ligações de parceiros (partner link) devem estar associadas a um tipo de ligação entre parceiros (partner link type) definidos na especificação de web services no WSDL;

Variables: seção que define as variáveis de dados usadas pelo Processo de Negócio. As definições são feitas em termos de tipos de mensagem WSDL, elementos ou tipos simples de esquemas XML. Elas são usadas para manter os dados de estado e o histórico do processo com base nas mensagens trocadas. As variáveis devem estar associadas a tipos de mensagens (messages) definidos na especificação WSDL;

FaultHandlers: seção que contém os tratadores de falhas que definem as atividades a ser executadas em resposta às falhas resultantes da invocação de serviços de avaliação e de aprovação;

Activities: seção que contém a descrição do comportamento normal para a execução do Processo de Negócio. Existem basicamente dois tipos de atividades:

• Basic Activity: tipo de atividade usado para executar alguma operação. Algumas dessas atividades básicas envolvem a interação com algum parceiro, as quais são: invoke, receive e reply. Outras dessas atividades básicas são executas sem a interação com qualquer parceiro, as quais são: wait, terminate, assign, empty, throw e compensate;

• Structured Activity: tipo de atividade usado para agrupar atividades básicas (basic activities) dentro de algumas estruturas de fluxo. Tais atividades são: while, pick, flow, sequence, switch e scope.

Abaixo está um gráfico de exemplo de um processo BPEL.

Repository

O Repository faz o intermédio entre o domínio e as camadas de mapeamento de dados, agindo como uma coleção de objetos em memória pertencente ao domínio. Objetos clientes constroem consultas declarativas e as submetem ao Repositório para que as possam satisfazer. A partir de uma simples coleção, objetos podem ser adicionados e removidos do repositório, de forma que o código de mapeamento é encapsulado pelo Repositório que irá realizar as operações apropriadas nos bastidores. Conceitualmente, um repositório encapsula o conjunto de objetos persistidos em um armazenamento de dados e as operações executadas sobre eles, proporcionando uma visão orientada a objetos da camada de persistência, separando de forma coesa a dependência entre o domínio e as camadas de mapeamento de dados.

Pensando em DDD, Repository é um padrão conceitual simples para encapsular soluções de SQL, mapeamento de dados (ORM, DAO, e etc), fábricas e trazer de volta o foco no modelo. Quando o desenvolvedor tiver construído uma consulta SQL, transmitindo essa consulta para um serviço de consultas da camada de infraestrutura, obtendo um RecordSet que extrai as informações e transmite para um construtor ou fábrica, o foco do modelo já não existe mais. Repository representa todos objetos de um determinado tipo como sendo um conjunto conceitual. Ele age como uma coleção, exceto por ter recursos mais elaborados para consultas. Essa definição une um conjunto coeso de responsabilidades para fornecer acesso às raízes dos agregados desde o início do clico de vida até o seu final. Em outras palavras, criar uma entidade com seus atributos agregados, por exemplo: Endereço > Logradouro > Bairro > Cidade >> Estado >> País.

Uma Fabric cuida do início da vida de um objeto, um Repository ajuda a controlar o meio e o final. Nesse caso o Repository cria objetos baseados em dados, vindos de um banco de dados, arquivo ou outros, o que no nível técnico não deixa de ser um Fabric. Mas para o DDD, Fabric e Repository tem responsabilidades distintas, Fabric cria novos objetos e Repository encontra objetos antigos, dando sempre a ilusão de que estão em memória. O que podemos fazer com elegância é combinar os dois patterns.

Umas das principais vantagens do Repository são:

Oferecer aos clientes um modelo simples para obter objetos persistentes e controlar seu ciclo de vida.
Tomam o design do aplicativo e do domínio e os desacoplam da tecnologia de persistência, de várias estratégias do banco de dados e até de várias fonte de dados. Ex: Database, Web Services e Xml.
Comunicam decições de design sobre acesso a objetos.
Permitem fácil substituição de uma implementação fake para o uso de teste (como uso de coleção em memória).

Diagrama de sequência:

Ofereça métodos que selecionam objetos baseados em critérios e retornem objetos completamente instanciados ou coleções de objetos cujos valores de atributos satisfaçam os critérios. Crie Repositórios somente para as entidades raízes de agregados que realmente precisem de acesso direto, ou seja, nem todas as entidades precisam de um repositórios e objetos de valor devem ser criados por Fabrics.

Repository encapsulando o armazenamento de dados:

Repository com Factory:

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.