Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Arquitetura evolutiva e design emergente: Usando DSLs

Capture padrões de domínio idiomático usando linguagens específicas do domínio

Neal Ford, Application Architect, ThoughtWorks Inc.
Neal Ford
Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Resumo:  Até agora, a arquitetura evolutiva e o design emergente focaram principalmente no design emergente para padrões técnicos. Este artigo abrange a captura de padrões idiomáticos de domínio usando técnicas de domain-specific language (DSL). Neal Ford, o autor da séria, ilustra a abordagem com um exemplo e mostra os benefícios desse estilo de abstração para a coleta de padrões idiomáticos.

Visualizar mais conteúdo nesta série

Data:  17/Mai/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  814 visualizações
Comentários:  


Os padrões idiomáticos podem ser técnicos ou de domínio. Os padrões técnicos representam soluções para os problemas comuns de software técnico, como a manipulação de dados de validação, de segurança e transacionais no seu aplicativo (ou conjunto de aplicativos). Os artigos anteriores focaram na coleta de padrões idiomáticos técnicos usando técnicas como a metaprogramação.Os padrões de domínio consideram como simplificar os problemas comuns de negócios. Enquanto os padrões técnicos estão presentes em praticamente todos os tipos de software, os padrões de domínio são distintos, assim como os negócios. No entanto, existe um rico conjunto de técnicas para coletá-los, que são o assunto deste e dos próximos artigos desta série.

Sobre esta série

Essa série visa fornecer uma perspectiva atual sobre os conceitos sempre discutidos, mas difíceis de compreender, de arquitetura e design de software. Por meio de exemplos concretos, Neal Ford fornece uma base sólida nas práticas ágeis da arquitetura evolutiva e do design emergente. Ao adiar importantes decisões de arquitetura e design até o último instante, é possível evitar uma complexidade desnecessária com base em questionamentos de seus projetos de software.

Este artigo fornece motivação para uso das técnicas de DSL como um estilo de abstração para coleta de padrões de domínio. As DSLs oferecem uma diversidade de opções, incluindo a própria nomenclatura padrão. O livro mais recente de Martin Fowler é uma investigação profunda das técnicas de DSL (consulte a seção Recursos). Eu irei usar muitos dos nomes de padrão dele e uma combinação dos meus exemplos e dos exemplos dele nos artigos subsequentes ao mostrar técnicas específicas.

Motivação para DSLs

Por que criar uma DSL somente para coletar padrões idiomáticos? Conforme mencionei no artigo "Aproveitando códigos reutilizáveis, Parte 2", uma das melhores formas de distinguir um padrão idiomático do resto do seu código é mudando sua aparência. Essa diferenciação visual é uma pista imediata de que você não está olhando para uma API regular. De forma similar, um dos objetivos do uso de DSLs é escrever códigos que se parecem menos com o código de origem e mais com o problema que você está tentando resolver. Se for possível atingir esse objetivo (ou mesmo chegar mais perto do objetivo do que você está agora), você preencherá uma lacuna importante da maioria dos projetos de software: a comunicação entre os desenvolvedores e as partes interessadas de negócios. Permitir aos usuários a leitura do seu código é um excelente benefício, pois elimina a necessidade de conversão do seu código para o idioma local, uma tarefa que induz ao erro. Ao tornar o seu código legível para pessoas não técnicas que conhecem a finalidade do software, é possível conversar mais claramente com eles.

Com o objetivo de motivar o uso desta técnica, irei usar um dos exemplos do Fowler do seu livro sobre DSL (consulte a seção Recursos). Digamos que eu trabalhe para uma empresa que cria compartimentos secretos controlados por software (pense no James Bond). Um dos clientes da minha empresa, a Sra. H., deseja a instalação de um compartimento secreto no seu quarto. No entanto, minha empresa usa torradeiras equipadas com Java™, que sobraram do fiasco da comercialização on-line, para executar o software. Apesar das torradeiras serem baratas, a colocação do software nelas é cara. Então, eu preciso criar o código básico do compartimento secreto e colocá-lo permanentemente nas torradeiras, e descobrir uma maneira de configurar as necessidades do compartimento secreto de cada cliente individualmente. Como você pode ver, esse é um problema comum no mundo de software moderno: comportamentos gerais que não são alterados com frequência, complementados com configurações que são alteradas para circunstâncias individuais.

A Sra. H. deseja um compartimento secreto que abre quando a porta do quarto é fechada pela primeira vez, e em seguida abre a segunda gaveta da penteadeira e, por fim, acende a luz da cabeceira da cama. Essas atividades devem acontecer na sequência, e se alguma coisa quebrar a sequência, será necessário reiniciar do começo. É possível imaginar o software que controla o compartimento secreto da Sra. H. como a máquina de estado ilustrada na Figura 1:


Figura 1. Compartimento secreto da Sra. H. como uma máquina de estado

A API da máquina de estado subjacente é simples. Eu criei uma classe de eventos abstratos, mostrada na Listagem 1, que manipula os eventos e os comandos na máquina de estado:


Listagem 1. Eventos abstratos para máquina de estado

public class AbstractEvent {
  private String name, code;

  public AbstractEvent(String name, String code) {
    this.name = name;
    this.code = code;
  }
  public String getCode() { return code;}
  public String getName() { return name;}   

É possível modelar os estados na máquina de estado com outra classe simples chamada States, mostrada na Listagem 2:


Listagem 2. O início da classe da máquina de estado

public class States {
  private State content;
  private List<TransitionBuilder> transitions = new ArrayList<TransitionBuilder>();
  private List<Commands> commands = new ArrayList<Commands>();

  public States(String name, StateMachineBuilder builder) {
    super(name, builder);
    content = new State(name);
  }

  State getState() {
    return content;
  }

  public States actions(Commands... identifiers) {
    builder.definingState(this);
    commands.addAll(Arrays.asList(identifiers));
    return this;
  }


  public TransitionBuilder transition(Events identifier) {
    builder.definingState(this);
    return new TransitionBuilder(this, identifier);
  }

  void addTransition(TransitionBuilder arg) {
    transitions.add(arg);
  }


  void produce() {
    for (Commands c : commands)
      content.addAction(c.getCommand());
    for (TransitionBuilder t : transitions)
      t.produce();
  }
}

A Listagem 1 e a Listagem 2 estão aqui somente para referência. O problema interessante a ser resolvido é a representação da configuração da máquina de estado. Essa representação é um padrão idiomático para o negócio de instalação de compartimentos secretos. A Listagem 3 mostra uma configuração baseada em Java para a máquina de estado:


Listagem 3. Uma opção de configuração: Código Java

Event doorClosed = new Event("doorClosed", "D1CL");
Event drawerOpened = new Event("drawerOpened", "D2OP");
Event lightOn = new Event("lightOn", "L1ON");
Event doorOpened = new Event("doorOpened", "D1OP");
Event panelClosed = new Event("panelClosed", "PNCL");

Command unlockPanelCmd = new Command("unlockPanel", "PNUL");
Command lockPanelCmd = new Command("lockPanel", "PNLK");
Command lockDoorCmd = new Command("lockDoor", "D1LK");
Command unlockDoorCmd = new Command("unlockDoor", "D1UL");

State idle = new State("idle");
State activeState = new State("active");
State waitingForLightState = new State("waitingForLight");
State waitingForDrawerState = new State("waitingForDrawer");
State unlockedPanelState = new State("unlockedPanel");

StateMachine machine = new StateMachine(idle);

idle.addTransition(doorClosed, activeState);
idle.addAction(unlockDoorCmd);
idle.addAction(lockPanelCmd);

activeState.addTransition(drawerOpened, waitingForLightState);
activeState.addTransition(lightOn, waitingForDrawerState);

waitingForLightState.addTransition(lightOn, unlockedPanelState);

waitingForDrawerState.addTransition(drawerOpened, unlockedPanelState);

unlockedPanelState.addAction(unlockPanelCmd);
unlockedPanelState.addAction(lockDoorCmd);
unlockedPanelState.addTransition(panelClosed, idle);

machine.addResetEvents(doorOpened);


A Listagem 3 destaca diversos problemas com o uso do código Java para a configuração da máquina de estado. Em primeiro lugar, não fica óbvio na leitura que essa é a configuração de uma máquina de estado. Como ocorre com muitas APIs Java, é uma pilha de códigos que não são diferenciados. Em segundo lugar, ela é muito detalhada e repetitiva. Por exemplo: os nomes das variáveis são usados repetidamente à medida que defino diversos estados e transições para cada parte da máquina de estado. Toda essa duplicação dificulta a leitura do código. Em terceiro lugar, esse código não atinge o objetivo original de configuração dos compartimentos secretos sem a recompilação do código.

Na verdade, quase não se vê códigos como esse no mundo Java, que prefere usar XML para o código de configuração. A composição da configuração em XML é simples, conforme mostrado na Listagem 4:


Listagem 4. Configuração da máquina de estado em XML

<stateMachine start = "idle">
    <event name="doorClosed" code="D1CL"/>
    <event name="drawerOpened" code="D2OP"/>
    <event name="lightOn" code="L1ON"/>
    <event name="doorOpened" code="D1OP"/>
    <event name="panelClosed" code="PNCL"/>

    <command name="unlockPanel" code="PNUL"/>
    <command name="lockPanel" code="PNLK"/>
    <command name="lockDoor" code="D1LK"/>
    <command name="unlockDoor" code="D1UL"/>

  <state name="idle">
    <transition event="doorClosed" target="active"/>
    <action command="unlockDoor"/>
    <action command="lockPanel"/>
  </state>

  <state name="active">
    <transition event="drawerOpened" target="waitingForLight"/>
    <transition event="lightOn" target="waitingForDrawer"/>
  </state>

  <state name="waitingForLight">
    <transition event="lightOn" target="unlockedPanel"/>
  </state>

  <state name="waitingForDrawer">
    <transition event="drawerOpened" target="unlockedPanel"/>
  </state>

  <state name="unlockedPanel">
    <action command="unlockPanel"/>
    <action command="lockDoor"/>    
    <transition event="panelClosed" target="idle"/>
   </state>

  <resetEvent name = "doorOpened"/>
</stateMachine>         

O código na Listagem 4 possui diversas vantagens em relação à versão Java. Em primeiro lugar, existe a ligação tardia, o que significa que é possível fazer alterações no código de configuração e colocá-las na torradeira, permitindo a um analisador XML a leitura da nova configuração. Em segundo lugar, para esse problema específico, esse código é muito mais expressivo, pois o XML inclui o conceito de contêiner: os estados incluem suas configurações como elementos filho. Isso ajuda a remover a redundância irritante que está presente na versão Java. Em terceiro lugar, esse código é naturalmente declarativo. Muitas vezes, o código declarativo é mais fácil de ler se estiver somente fazendo instruções e for utilizar as sintaxes if e while .

Retroceda um pouco e pense nas implicações. A externalização da configuração é um padrão tão comum no mundo Java moderno que nem pensamos nela mais como uma entidade distinta. No entanto, esse é um recurso de praticamente todas as estruturas Java. A configuração é um padrão idiomático e precisamos buscar formas de capturá-lo separando-o e diferenciando-o do comportamento geral da estrutura circundante. Ao usar o XML para a configuração, estou escrevendo o código em uma DSL externa (a sintaxe é XML e a gramática é definida pelo esquema associado a esse documento XML), então não é necessário recompilar o meu código de estrutura para alterá-lo.

Não é necessário ir até o fim em XML para obter as vantagens do XML. Considere a versão do código de configuração mostrado na Listagem 5:


Listagem 5. Uma configuração de máquina de estado com gramática customizada

events
  doorClosed  D1CL
  drawerOpened  D2OP
  lightOn     L1ON
  doorOpened  D1OP
  panelClosed PNCL
end

resetEvents
  doorOpened
end

commands
  unlockPanel PNUL
  lockPanel   PNLK
  lockDoor    D1LK
  unlockDoor  D1UL
end

state idle
  actions {unlockDoor lockPanel}
  doorClosed => active
end

state active
  drawerOpened => waitingForLight
  lightOn    => waitingForDrawer
end

state waitingForLight
  lightOn => unlockedPanel
end

state waitingForDrawer
  drawerOpened => unlockedPanel
end

state unlockedPanel
  actions {unlockPanel lockDoor}
  panelClosed => idle
end

Essa versão do código possui muitos benefícios da versão XML: é declarativa, possui o conceito de contêiner e é concisa. Ela é mais vantajosa em comparação as versões XML e Java, pois possui menos caracteres de ruído (como < e >) que são necessários para a implementação técnica, mas prejudicam a capacidade de leitura.

Essa versão do código de configuração é uma DSL externa customizada escrita usando ANTLR, uma ferramenta de software livre que facilita a composição de idiomas customizados (consulte a seção Recursos). Aqueles que ainda têm pesadelos com a classe de compilador apresentada na universidade (incluindo ferramentas clássicas como Lex e YACC) ficarão felizes de saber que as ferramentas foram muito aprimoradas. Esse exemplo é do livro do Fowler e, segundo ele, o desenvolvimento da versão XML e o desenvolvimento da versão da linguagem customizada demoram praticamente a mesma quantidade de tempo.

A Listagem 6 contém outra alternativa, escrita em Ruby:


Listagem 6. Configuração da máquina de estado em JRuby

event :doorClosed, "D1CL"
event :drawerOpened, "D2OP"
event :lightOn, "L1ON"
event :doorOpened, "D1OP"
event :panelClosed, "PNCL"

command :unlockPanel, "PNUL"
command :lockPanel, "PNLK"
command :lockDoor, "D1LK"
command :unlockDoor, "D1UL"

resetEvents :doorOpened

state :idle do
  actions :unlockDoor, :lockPanel
  transitions :doorClosed => :active
end

state :active do
  transitions :drawerOpened => :waitingForLight,
              :lightOn => :waitingForDrawer
end

state :waitingForLight do
  transitions :lightOn => :unlockedPanel
end

state :waitingForDrawer do
  transitions :drawerOpened => :unlockedPanel
end

state :unlockedPanel do
  actions :unlockPanel, :lockDoor
  transitions :panelClosed => :idle
end   

Esse é um bom exemplo de uma DSL interna : uma DSL que usa a sintaxe da linguagem de base, o que significa que essa DSL deve ser um código Ruby válido, no que diz respeito à sintaxe. (Pois é escrito em Ruby, é possível executá-lo através do JRuby, o que significa que todas as suas torradeiras precisam ter o arquivo JRuby JAR.)

A Listagem 6 possui muitas vantagens da linguagem customizada. Observe o uso intenso de blocos de Ruby para agir como contêineres, o que fornece o mesmo tipo de contêiner semântico do XML e das versões de linguagens customizadas. Ele usa mais caracteres de ruído do que a linguagem customizada. Por exemplo: o prefixo : no Ruby indica um símbolo, que nesse caso é basicamente uma cadeia de caractere imutável usada como um identificador.

A implementação desse tipo de DSL em Ruby é bem simples, conforme mostrado na Listagem 7:


Listagem 7. Definição de classe parcial para a DSL JRuby

class StateMachineBuilder
  attr_reader :machine, :events, :states, :commands

  def initialize
    @events = {}
    @states = {}
    @state_blocks = {}
    @commands = {}
  end

  def event name, code
    @events[name] = Event.new(name.to_s, code)
  end

  def state name, &block
    @states[name] = State.new(name.to_s)
    @state_blocks[name] = block
    @start_state ||= @states[name]
  end

  def command name, code
    @commands[name] = Command.new(name.to_s, code)
  end

O Ruby possui regras flexíveis sobre sintaxe, o que o torna apropriado para esse tipo de DSL. Por exemplo: ao declarar um evento, não é necessário incluir os parênteses como parte da chamada de método. Nessa versão, não é necessário escrever sua própria linguagem ou se prejudicar com sinais de maior e menor. Isso ajuda a ilustrar porque essa abordagem é tão popular no mundo Ruby.


Características das DSLs

A DSL oferece uma boa sintaxe alternativa para coleta de padrões idiomáticos. De acordo com a definição de Martin Fowler, as DSLs possuem cinco características principais.

Linguagem de programação de computador

Para ser uma DSL, uma linguagem precisa ser uma linguagem de programação de computador. Sem essa restrição em vigor, é fácil chegar a um caminho perigoso no qual todos os itens encontrados são uma DSL. Se o termo DSL for definido dessa forma abrangente, todas as conversas contextualizadas seriam DSLs. Por exemplo: eu possuo colegas que são fãs de críquete. Quando eu passo por eles quando eles estão conversando sobre críquete, não consigo entender o que eles estão falando, apesar deles estarem usando palavras em inglês. Eu não possuo o contexto apropriado para entender a forma que eles estão usando as palavras. Então, seria possível argumentar que críquete e outros esportes possuem DSLs nas suas terminologias. Mas com essa definição ampla fica difícil limitá-la para restrições úteis — por isso a insistência do Fowler com a restrição para linguagens de programação de computador.

Natureza da linguagem

O segundo critério do Fowler para as DSLs é que elas tenham uma "natureza de linguagem", o que significa que a DSL deve ser vagamente legível por indivíduos que não são programadores. Essa natureza da linguagem pode ter várias formas, muitas das quais mostrarei nos próximos artigos, nos quais continuarei investigando o uso das DSLs como uma forma de capturar padrões idiomáticos.

Foco no domínio

Para ser uma DSL adequada, a linguagem deve ser focada de forma restrita em um domínio de problema particular. Um dos riscos da tentativa de criação de DSLs é torná-las muito amplas. As DSLs são um mecanismo de abstração e a criação de uma abstração muito ampla diminui os benefícios da abstração.

Expressividade limitada

A expressividade limitada também é uma característica típica das DSLs. Por exemplo: é muito raro encontrar uma DSL que inclua estruturas de controle como looping e decisões. A DSL deve focar de forma especial e exclusiva no domínio que está tentando descrever. Como resultado, a maioria das DSLs é declarativa e não imperativa.

Não é Turing completa

Os dois critérios anteriores sugerem essa característica, mas irei formalizá-la aqui. A DSL não deve ser Turing completa (consulte a seção Recursos). Na verdade, a transformação acidental para o padrão Turing completo é considerada um antipadrão nas DSLs. Por exemplo: o arquivo clássico de configuração do UNIX® sendmail é acidentalmente Turing completo. Seria possível escrever um sistema operacional no arquivo de configuração sendmail se desejar e tiver bastante tempo livre.

É surpreendentemente fácil se tornar Turing completo acidentalmente. Algumas ferramentas de infraestrutura familiar fizeram essa transição acidentalmente — o XSLT, por exemplo. Ocasionalmente, a determinação de uma linguagem como DSL depende do contexto no qual a linguagem está sendo usada. Ao usar XSLT para transformar uma versão de texto em outra versão de texto, a XSLT é usada como uma DSL. Ao usar XSLT para resolver o problema das Torres de Hanói, a XSLT é usada como uma linguagem Turing completa (e você provavelmente deveria encontrar um novo hobby).


Resumo

Neste artigo, eu lancei a base para o uso de DSLs como um mecanismo de extração para coleta de padrões idiomáticos. As DSLs funcionam muito bem para esse propósito, pois são facilmente diferenciadas das APIs regulares, tendem a ser de natureza declarativa e melhoram o loop de feedback de comunicação entre os desenvolvedores e não desenvolvedores nos projetos. Nos próximos artigos, irei investigar diversas técnicas para desenvolvimento de DSLs. Nos próximos artigos, demonstrarei também diversas técnicas de DSL que poderão ser aproveitadas na sua busca para descoberta e design no código.


Recursos

Aprender

Discutir

Sobre o autor

Neal Ford

Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. Projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD, e é autor ou editor de livros que abordam uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=658138
ArticleTitle=Arquitetura evolutiva e design emergente: Usando DSLs
publish-date=05172011
author1-email=nford@thoughtworks.com
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).