Domain Specific Languages: Do problema à DSL

January 8th, 2008 § 6 comments

Este é o segundo artigo em uma série sobre DSL, ou Linguagens Específicas de Domínio. O primeiro artigo introduziu o assunto explicando o que é uma DSL, como elas podem melhorar e simplificar o código de uma aplicação e, finalmente, mostrando alguns exemplos usados pela maioria dos desenvolvedores.

Neste segundo artigo, explicaremos com um problema comum pode ser transformado em uma DSL para exemplificar o processo de pensamento por trás da criação de uma linguagem que se propõe a resolver um problema específico em uma aplicação.

Como veremos abaixo, uma DSL pode converter um problema espinhoso em algo relativamente trivial. Para dar uma idéia do impacto desse tipo de simplificação, uma das aplicações que eu desenvolvi recentemente envolve a análise de segurança de barragens. Como todo consultor de segurança diria, barragens tendem sempre a cair e o problema é saber quando e como evitar isso. Para descobrir esses dados, o programa agrega diversas informações e permite que o consultor extrapole previsões de risco para a instalação e tome as medidas apropriadas.

Uma das mais importantes informações adquiridas pelo programa são dados de instrumentação. Esses dados podem ser digitados no sistema ou coletados automaticamente em leitores apropriados. Para um dado cliente, o programa pode suportar se meia dúzia a milhares de instrumentos variante de dez a quarenta tipos diferentes. Em muitos casos, uma nova instalação do programa também evolve a criação de uma interface para um novo tipo de instrumento e tudo o que está associado ao mesmo: leituras, programação, projeções, gráficos, impressão e uma série de outros benefícios.

A estrutura criada pelo programa é relativamente flexível e permite a inserção de novos instrumentos de uma maneira comparativamente simples. O problema é integrar toda a lógica relacionada a um instrumento no resto do sistema. Da maneira como o programa está estruturado, isso é feito por classes específicas que lidam com cada tipo de instrumento. E, embora a integração final seja simples, testar cada um desses instrumentos é algo demorado.

A solução encontrada foi criar uma DSL para descrever especificações de instrumentos e criar toda a infra-estrutura necessária. Testando a DSL uma única vez seria a garantia de que todos instrumentos estariam automaticamente testados e não seria necessário nos preocuparmos com cada detalhe de implementação. Isso foi feito e o tempo de incorporação de um novo instrumento caiu de uma semana para duas horas. Os erros são mínimos e a aplicação tem funcionado bem ao contento de seus usuários.

Esse é o poder de uma DSL.

Um problema

Para mostrar essa variação de expressividade de uma forma bem prática, vamos imaginar um problema relativamente simples, também baseado em uma experiência recente minha. A solução que encontramos é parecida ao que Martin Fowler descreve em seu artigo sobre o mesmo tema de modo que toda similaridade não é coincidência.

Em uma das nossas aplicações, um dos problemas a serem resolvidos era a importação diária de várias planilhas que chegam em um centro de produção do cliente. Esses arquivos são gerados por aplicações de terceiros, aos quais não tínhamos acesso e variavam constantemente em seu formato. Alguns de mês para mês, outros de semana para semana. Parte do contrato, portanto, era criar uma forma simples de mapear esses arquivos de modo que mudanças pudessem ser tratados com eficiência. De fato, com um pouco de treinamento, mesmo os usuários da aplicação seriam capazes de elaborar novos mapeamentos e lidar com as variações mais comuns da importação.

Para facilitar, o exemplo será dado em Ruby, embora a linguagem original tenha sido o C# dentro de uma aplicação usando ASP.NET.

A maneira convencional de resolver um problema assim é criar um framework de processamento. Isso incluiria:

  • Uma classe para descrever formatos de leitura dos arquivos
  • Uma classe para descrever configurações de ambiente
  • Uma classe para ler arquivos que receberia esses formatos e devolveria registros processados
  • Uma classe para mapear os registros processados e passá-los a um serviço interno da aplicação
  • Uma classe para juntar todas acima em uma interface mais simples

Uma solução

Uma implementação simples, consistiria então de algo assim:

class Configuration
  def initialize(config_file); end
end

class Format
  def initialize(configuration); end
  def define_field(name, column, type, options); end
end

class Reader
  def initialize(configuration, filename, format); end
  def process; end
end

class Service; end
class MyService < Service; end

Essas classes seriam usadas da seguinte forma:

config = Configuration.new("config.yaml")

format = Format.new(config)
format.define_field("name", "a1", String)
format.define_field("date", "a2", Date, :year => Date.today.year)
format.define_field("order_id", "a3", Integer, :unique => true)

service = MyService.new

reader = Reader.new(config, "data.csv", format)

reader.process do |item|
  service.process(item.name, item.date, item.order_id)
  service.dispatch
end

Esse processo de resolução do problema é trivial e fácil de ser acompanhado. De fato, essa a estratégia que a maioria dos programadores escolhe ao resolver esse tipo de situação. A solução é flexível, pode ser entendida facilmente na presença de relativamente pouca documentação, e não oferece desafios à implementação.

A única coisa que a implementação acima não faz, e isso é o mais importante, é declarar o intento do processo que está sendo codificado. E aqui entra o maior valor de uma DSL.

Intento

O maior valor que uma DSL pode trazer a um problema é deixar claro o intento do mesmo na solução. Uma DSL, pela sua proximidade com uma linguagem de programação, possui a expressividade dessas últimas mas procura ser limitada naquilo que pode expressar justamente para conseguir reduzir a complexidade adjacente.

Se uma DSL é expandida com toda sorte de construções, ela deixa de ser uma DSL e se torna uma linguagem completa. Isso não impede é claro, que linguagens completas sejam implementadas com características de uma DSL. De fato, macros em Lisp ou Scheme e muito da gramática de Smalltalk são estruturadas para facilitar a criação de construções idiomáticas dentro de um programa que podem ser usadas com uma forma de DSL.

Em última instância, o objetivo de uma DSL, então, é fornecer intento ao código, ou seja, fazer com que o mesmo seja auto-documentado, que o próprio fato de ler o trecho em questão já revele o necessário para a compreensão mesmo. Uma DSL não é senão uma outra forma de abstrair a complexidade. Nesse sentido, elas sucedem historicamente a programação estrutura e a orientação a objetos, algo sobre o qual voltaremos a falar em breve.

Reduzindo o problema

Considerando a implementação mostrada, que, embora não seja muito complexa, não descreve apropriadamente o intento do código, como podemos convertê-la para uma DSL que faça isso? A solução é relativamente simples:

process_file :named => "data.csv", :using => MyService do
  map_column :a1, :to_field => :name, :formatted_as => String
  map_column :a2, :to_field => :date, :formatted_as => Date,
    :with_options => { :year => Date.today.year }
  map_column :a3, :to_field => :order_id, 
    :formatted_as => Integer,
    :with_options => { :unique => true }
end

Uma outra opção seria:

process_file("data.csv").using(MyService) do
  map_column(:a1).to_field(:name).formatted_as(String)
  map_column(:a2).to_field(:date).formatted_as(Date).
    with_options(:year => Date.today.year)
  map_column(:a3).to_field(:order_with).formatted_as(Integer).
    with_options(:unique => true)
end

Ambas são igualmente válidas no sentido que declaram o intento do código e podem ser lidas quase como um texto descrevendo o que estão executando.

É importante lembrar que uma DSL esconde a complexidade do código–ele não elimina a necessidade de escrever o código. Uma DSL bem projetado, entretanto, pode reduzir o código a ser escrito codificando instruções comuns em formatos mais acessíveis. Embora o caso mostrado acima seja um caso simples, a redução de código chega a 30% em termos brutos e com um ganho imenso em termos de legibilidade.

Em nosso próximo artigo, veremos a implementação de uma DSL em Ruby e como aproveitar os recursos que uma linguagem com meta-programação oferece para isso.

§ 6 Responses to Domain Specific Languages: Do problema à DSL"

  • Leonardo Goslar Otto says:

    Não sei se estou enganado mas isso parece mais com uma Fluent Interface do que com uma dsl.

  • Ronaldo says:

    Sim, o último exemplo também poderia ser descrito nesses termos. Como Martin Fowler–um dos criadores do termo–aponta em seu artigo sobre o assunto, os termos são intercambiáveis para determinados tipos de DSL.

    Eu não faço muita distinção na prática porque, dependendo da linguagem, só um sabor das mesmas é possível. Se você tem uma linguagem mais estática, às vezes é melhor reformatar uma DSL como uma Fluent Interface para facilitar o uso. No meu trabalho com C#, inclusive, é que eu geralmente faço.

  • Samir says:

    Ronaldo parabens!

    Tenho uma dúvida, no caso do Rails por exemplo, eu poderia escrever uma DSL mesclada com o poder do ActiveRecord para regras de negócio num sistema financeiro, ou to falando abobrinha :-)

    abraços

  • Ronaldo says:

    Opa, Samir. Você poderia sim. Se fosse interna, você pode inclusive usar os vários ganchos que o próprio ActiveRecord fornece como callbacks e filtros para fazer algo bem interessante. Se fosse externa, o maior problema já seria lidar com erros caso os usuários se desviem do padrão. Nesse caso, talvez um parsing anterior para sanear a DSL seja necessário.

  • Samir says:

    Entendi, no meu caso acho que seria somente as internas de principío, talvez você poderia citar alguns exemplos nos próximo artigos, vai ajudar bastante a esclarecer de fato a utilização da DSL.

    abraços

  • Ronaldo says:

    Os exemplos estão vindo mas sem o ActiveRecord no momento. Vou pensar em alguma coisa e dependendo posto lá. :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

What's this?

You are currently reading Domain Specific Languages: Do problema à DSL at Superfície Reflexiva.

meta