Introdução ao Output-Oriented Design
Simplicidade e agilidade para estruturar o desenvolvimento
Saiba por onde começar e por onde seguir depois. Um método simples e ágil para estruturar o planejamento do desenvolvimento de software, que respeita as dependências entre entradas, processamento, saídas e persistência e que possibilita entregas regulares, além de um versionamento preciso das entregas.
A estrutura primária
Em essência, todo sistema de informação tem a estrutura entrada-processamento-saída. Como a geração das saídas é assíncrona, é preciso ter também a memória ou persistência.
Há algumas dependências aqui. A primeira é que as saídas são geradas a partir da persistência, então esta tem que ter os dados necessários para gerar as saídas. A persistência é alimentada pelas entradas, então estas têm que suprir os dados necessários para popular a persistência.
Para fazer as definições dos requisitos, portanto, primeiro precisamos saber quais serão as saídas para poder definir a persistência, para então poder definir as entradas.
Essa é a ideia básica do Output-Oriented Design: começamos pelas saídas e trabalhamos de frente para trás na direção da persistência e depois das entradas. O Output-Oriented Design foi proposto em 1981 por Ken Orr, no livro Structured Requirements Definition, que a propósito ainda pode ser encontrado on-line.
Como exemplo, considere um sistema para controle de aluguéis de imóveis em uma imobiliária. Procuramos primeiro as saídas; no caso, uma básica é o contrato.
A partir de amostras de contratos, buscamos então os dados necessários: do imóvel, do locatário, do locador, condições contratuais. Podemos então modelar a persistência. Ao fazermos isso, identificamos objetos de negócio que, na direção da persistência, vão se tornar tabelas, e, na direção das entradas, vão se dar origem a funcionalidades do tipo CRUD: Manter Imóveis, Manter Pessoas (supondo que locatários e locadores são semelhantes), Manter Contratos.
Note como podemos ter segurança das definições: saídas estáveis asseguram persistência estável, que por sua vez assegura entradas estáveis.
saídas estáveis -> persistência estável -> entradas estáveis
Claro que erros terão impacto proporcional à posição do erro no fluxo: uma omissão em uma saída, por exemplo, irá impactar a persistência que por sua vez irá impactar a entrada.
Embora seja simples, essa estrutura nem sempre fica óbvia. Por exemplo, para o desenvolvimento de um sistema de um grande órgão público, o cliente forneceu uma estrutura de fases com as funcionalidades de cada uma, sendo a primeira Cadastros. As entradas eram as primeiras, e isso certamente iria implicar em retrabalho quando as demais funcionalidades fossem aprofundadas. Mais importante, para definir os cadastros foi preciso ter em mente os usos desses dados, que acabaram ficando implícitos.
Acelerando a implementação
Vimos acima que tendo a persistência definida podemos definir funcionalidades de entrada. O fato de termos uma saída também nos permite definir a funcionalidade de geração da saída. No caso do contrato de aluguel, será algo como Gerar Contrato Aluguel. Algo interessante pode ser feito imediatamente: começar a implementação dessa funcionalidade. Vamos supor que o contrato deva ser entregue no formato PDF. Podemos fazer assim:
- É estruturada a geração de arquivos PDF, com conteúdo qualquer.
- É gerada uma primeira versão do contrato, com dados hard-coded. Esta versão servirá para o gestor homologar a forma e o formato do contrato.
- É implementada uma interface que fornece os dados para geração do contrato e nova versão da funcionalidade de geração que usa essa interface. Aqui os dados do contrato ainda são “frios”.
- Estando disponível a persistência, nova versão é feita lendo-se dados da base, ainda frios.
- Quando houver dados quentes, uma nova versão da geração do contrato agora pode ser homologada como um todo.
Note como a implementação da funcionalidade para gerar o contrato é progressiva. Esse dividir para conquistar também permite que coisas mais simples sejam feitas e validadas a cada iteração.
Note também como essa estrutura possibilita entregas regulares, mesmo que intermediárias. De fato, a implementação pode ser iniciada imediatamente uma vez que se tenha uma saída definida.
A implementação de uma saída pode começar imediatamente após sua definição.
Um critério para quebra de versões
Se o sistema tiver mais de uma saída, outra possibilidade interessante se apresenta. Vamos supor que o sistema de aluguéis tem que fornecer listas de documentos que os envolvidos devem apresentar. Essas saídas serão trabalhadas como o contrato, de frente para trás. Uma questão surge: o que priorizar para detalhamento e implementação?
Assim como a função do desenvolvimento é entregar software, a função de um sistema de informação é entregar informação. Em outras palavras, um sistema terá valor na medida em que entregar informação útil para seus usuários. Além disso, um sistema é entregue em versões.
Tudo combinado, temos um critério de quebra de versões do sistema: as saídas que cada versão entregará. No caso dos aluguéis, a versão 1 entregará listas de documentos, a versão 2 entregará o contrato. Versões intermediárias podem entregar melhorias não estruturais, isto é, que não incluem novas saídas.
As saídas baseiam a quebra de versões maiores do sistema.
Como as saídas serão usadas na quebra de versões naturalmente pode variar. Por exemplo, fará sentido incluir em uma mesma versão saídas semelhantes.
Temos então um critério objetivo de quebra de versões do sistema. O gestor do sistema também pode priorizar saídas. E em qualquer caso o desenvolvimento estará entregando valor para os usuários. Compare isso com o exemplo acima, em que toda uma fase do desenvolvimento somente entregou funcionalidades de cadastro.
Questões de qualidade
Ótimo ter um critério tão objetivo como a identificação de saídas, mas alguns dificultadores podem surgir:
- Por onde começar?
- Como sabemos que todas as saídas estão definidas?
- Como sabemos se temos as saídas corretas?
- E se o pessoal de negócio não souber quais são as saídas?
Primeiro, vamos circunscrever um pouco as questões. Quando desenvolvemos um sistema, podemos estar informatizando algo existente ou fazendo algo novo. No primeiro caso, temos apenas que obter amostras das saídas.
Segundo, nós, não sendo do negócio, não teremos respostas, pelo menos não se pode assumir que teremos (embora já tenha visto casos em que desenvolvedores eram bem capazes de especificar requisitos).
Precisamos então de alguma ferramenta que permita uma interação produtiva entre desenvolvedores e gestores. Orr propõe o que ele chama de diagrama de entidades. Colocamos a entidade principal no centro (a empresa ou um órgão, por exemplo), as entidades com que ela se relaciona ao redor e registramos quem envia o que para quem.
Em seu livro, Orr indica também as entradas nos diagramas de entidades. Nossa preferência é por um diagrama semelhante, o diagrama de contexto (exemplo básico na figura), inicialmente representando somente as saídas.
Como com qualquer bom diagrama, temos um formato sintético e estruturado para representar (e documentar) as saídas de forma integrada e em um só campo visual. Claro, esse é um modelo de estrutura; os detalhes dos elementos devem estar disponíveis e acessíveis.
Nos casos em que não se sabe quais serão as saídas, pelo menos temos um objetivo claro: defini-las. Essa definição está no caminho crítico do desenvolvimento e deve ser feita antes que se possa continuar.
Saber mais?
Talvez você concorde que um método tão simples e poderoso poderia ter merecido mais repercussão, mas de fato isso não ocorreu, e há raros conteúdos sobre esse tema na internet. Nós podemos dar uma contribuição através de uma palestra que descreve o método e em seguida é discutido como aplicá-lo à realidade local. Se lhe interessar, faça contato pelo nosso e-mail.