Ao desenhar um software partimos de contratos entre componentes e vamos refinando esses contratos até que existe um código que pode ser implementado. As funcionalidades e responsabilidades dos objetos vão sendo definidas partindo do conceito e indo para o detalhe de implementação. Nesse ponto, entendemos que usar bibliotecas de terceiros nos podem ajudar de duas formas:

  • Acelerar o desenvolvimento – até poderíamos implementar as mesmas funcionalidades que a bibliotecas implementa – não está além do nosso conhecimento – mas iria demorar tempo que normalmente não temos.
  • Usufruir de conhecimento de terceiros – a biblioteca implementa algoritmos que são muito complexos ou muito difíceis de implementar corretamente, ou de testar. Utilizar uma biblioteca de terceiros nos permite usufruir de todo esse conhecimento sem ter que aprender como implementá-lo nós mesmos.

Contudo, estas vantagens não são livres de riscos.

  • Vulnerabilidade de Segurança – a biblioteca pode conter vulnerabilidades desconhecidas que deixam o sistema exposto a ataques de adversários mal intencionados. Normalmente podemos esperar a próxima versão corrigida, mas ficamos vulneráveis entretanto.
  • Sabotagem – a biblioteca é propositalmente sabotada para causar dano a todos os seus usuários. Teríamos que reverter a uma versão estável anterior, mas eventualmente, teríamos que substituí-la
  • Quebra de Compatibilidade – a biblioteca evolui com diferentes versões que não são compatíveis entre si. Pode implicar em refactorings extensos da base de código cada vez que a biblioteca muda.
  • Defeitos – a biblioteca contém defeitos que impedem o sistema de funcionar corretamente. Teremos que esperar que uma nova versão com a correção. Se a biblioteca for open source podemos até enviar um patch, se soubermos como corrigir, mas ainda tem que ser aprovado e a versão liberada. Entretanto o sistema não funciona corretamente.
  • Limitação – a biblioteca faz uma coisa correta e eficientemente, mas na realidade você descobriu que precisa de algum um pouco mais avançado ou configurável. Pode tentar substituir por outra mais avançada, mas irá incorrer em mudanças em toda a base de código, o que pode deixar o sistema instável e com bugs que não existiam antes
  • Dependências transitivas – a biblioteca de terceiros pode, ela mesma, depender de outras bibliotecas do mesmo autor ou de outros, multiplicado estes mesmos riscos pela cadeia de dependências. Aqui não há realmente nada a fazer senão escolher outra biblioteca com menos dependências.

Quando encaramos o design como a origem dos contratos e as bibliotecas de terceiros como meros aceleradores ou possibilitadores da implementação de ditos contratos, o conceito de isolamento é natural e decorre do próprio processo de design. Contudo, em muitos sistema o uso de bibliotecas vêm antes. Não é pensado que o sistema precisa de um mecanismo de logging, mas que queremos usar o Log4J ou o SLF4J. Não pensamos que queremos um design de contratos de camada de persistências simples e desacoplados, mas que queremos usar o banco de dados X ou Y que por sua vez ditam usar a biblioteca A ou B. Não pensamos como seria uma API de envio de email, mas usamos o SDK diretamente onde precisamos. Quando não há design incidente o uso das bibliotecas tente a ser direto, porque os contratos não vieram primeiro. Vieram as implementações.

Isto causa uma aparente dicotomia, ou um aparente trade-off que realmente não existe. Quando vemos do ponto de vista do design top-down, o isolamento é natural e decorre do próprio design. Quando vemos da construção bottom-up, parece artificial envolver as bibliotecas em uma casca adicional. Afinal só queremos utilizar as suas capacidades e já escolhemos a melhor biblioteca para que precisamos… ou assim achamos.

O problema é que os requisitos mudam. E quando mais tempo passa, maior a probabilidade de mudarem. Onde antes era simples e perfeitamente aceitável enviar um email diretamente por SMTP , a empresa agora quer usar um serviço externo que permite configurar as mensagens e a aparência externamente. Sem o isolamento provido pela separação entre o contrato de enviar email e a implementação tecnológica, fazer esta evolução pode ser bastante caro.

Normalmente quando utilizamos bibliotecas de terceiros é muito fácil focar nos benefícios e esquecer os riscos. Outras vezes, até sabemos dos riscos, mas os descartamos face a outras considerações que nos parecem mais importantes há época; normalmente tempo. Isso é uma grande armadilha.

Tempo de vida da aplicação e débito técnico

Desconsiderar ricos nunca é uma boa ideia, mas pode ser aceitável dependendo do tempo de vida da aplicação. Uma aplicação muito simples que pretendemos usar algumas poucas vezes não é impactada pelos riscos descritos, pois iremos jogá-la fora em breve. Contudo, tomar os mesmos riscos num produto que se espera durar 5 ou 10 anos, é insensato já que quanto mais o tempo passa, maior a probabilidade de um daqueles problemas descritos acontecer. E essa probabilidade é aumentada por cada dependência que o produto tem.

O foco deste artigo é em aplicações e sistema de vida longa. Para aplicações e sistema de vida curta, o problema não se coloca, pois é sempre previsto desde o inicio que o software será abandonado em algum momento. Uma aplicação de vida curta pode ser dar ao luxo de ser apenas uma amalgama de API de terceiros. Um produto de longa vida, não.

Para um produto de longa vida o uso de bibliotecas de terceiros atua como um acelerador. Sabemos que deveríamos implementar nós mesmos a funcionalidade, mas tomamos o débito técnico de usar a biblioteca de terceiros para acelerar o processo. Ou, não sabemos muito bem como se implementa aquela parte do código, e adotamos uma biblioteca de terceiros que sabem. Este conceito de sacrificar aquilo que sabemos que seria o ótimo, em troca de velocidade é conhecido como Débito Técnico, mas como todo o débito técnico, temos que planejar como voltar ao ponto ótimo no futuro. No caso, não necessariamente queremos remover a biblioteca, mas queremos deixar a porta aberta para que isso seja possível com o mínimo de esforço. No mínimo queremos poder substituí-la por outra. Isto demanda técnicas especiais que deixam o código preparado para a futura migração e pagamento do débito técnico. Isto demanda design.

Não é sensato esperar que nada aconteça com as nossas dependências, é improvável que elas se mantenham funcionando corretamente ao longo do tempo sem problemas. Mas também, não é sensato construir tudo o que outros já construiram. Tem que haver um meio termo, e isso tem que derivar de um design apropriado e incidente.

O meio termo

O meio termo é simples : vamos utilizar todas as bibliotecas de terceiros que precisamos, mas não vamos acoplar nosso código a elas. Portanto, quando for necessário – e irá ser necessário – podemos trocar a biblioteca por outra mais moderna, mais segura, ou até mais rápida, sem termos que mexer no nosso código de negócio. Para isso precisamos isolar e desacoplar nossas necessidades, das bibliotecas que as provêm.

Isolando e Desacoplando

As bibliotecas que usamos têm diferentes graus de acoplamento e diferentes graus de pulverização (scatering) dentro da nossa aplicação. Algumas bibliotecas como o Hibernate são apenas úteis em uma camada muito especifica do sistema. Outras como o Spring ou o EJB abraçam toda a aplicação formando um “container” . Outras ainda – como o Joda Time, ou o Log4J – podem estar espalhadas por todos os nossos objetos em qualquer camada.

Existe uma dependência física com o código da biblioteca: importações de classes e interfaces no nosso código. Depois, existe o acoplamento lógico entre a nossa aplicação e a biblioteca externa. É muito fácil cair na tentação de criar uma interface que replica a da biblioteca externa ou que requer as mesmas ações, na mesma ordem com as mesmas convenções. Este acoplamento é pior que o acoplamento físico. O acoplamento físico pode ser facilmente resolvido com uma interface diferente, com objetos , nomes e parâmetros diferentes. O acoplamento lógico só pode ser removido isolando as regras do domínio da biblioteca de forma a sabermos o que é o comportamento que queremos, e o que é detalhe da implementação da biblioteca.

Assim sendo, para cada cenário a forma de isolamento é ligeiramente diferente.Uma pequena variação da receita padrão.

Forma de isolamento padrão

Do ponto de vista do design, a receita para isolar uma biblioteca de outra – seja de terceiros ou nossa, é bem conhecida: utilizar interfaces. A receita padrão é utilizar uma, ou mais, interfaces cuja implementação pode ser trocada, por outra, em um único ponto do sistema.

Normalmente as solução para o isolamento de bibliotecas é identificada com o uso do padrão Adapter. Mas existem outros padrões que também podem ser usadas e que, embora possam ser um pouco mais complexos, são necessários em vários cenários. Por exemplo, Service, Strategy e Bridge.

Cada um dos padrões será mais útil em uma situação que em outra, e isso é normal. O que precisamos ter sempre atenção é que, qualquer que seja o contrato da nossa interface ele tem que ser baseado nas necessidades e requisitos do nosso sistema, e não em como a biblioteca de terceiros funciona.

Isolando bibliotecas úteis em uma camada

A forma mais simples de isolar uma biblioteca de terceiros é isolar a camada onde ela é usada. Se temos nossas camadas bem desenhadas e separadas umas das outras com contratos, e apenas usamos interfaces, é relativamente simples implementar a mesma interface com outra tecnologia.

Para isolar bibliotecas em uma camada, não precisamos isolar a API em si, mas o serviço que ela provê.

Neste caso não precisamos de métodos no nosso contrato que sejam 1-para-1 com os métodos da biblioteca que estamos usando, nem precisamos de replicar no nosso modelo cada conceito usado pela biblioteca. Apenas precisamos de um contrato sobre o que estamos tentando fazer e que esse contrato não dependa de objetos, classes, interfaces ou anotações da biblioteca de terceiros.

No exemplo do Hibernate, precisamos isolar os mecanismo de pesquisar, criar, editar e apagar linhas no banco de dados. O Hibernate vai demandar alguns ganchos e teremos que ter algumas classes amarradas com a sua API, pelo uso de interfaces, classes que temos que estender, ou anotações. Então, para isolar o nosso sistema do uso de tudo isso, colocamos todas essas classes em uma camada separada da aplicação isolada atrás de uma interface. Quando decidirmos usar outra API, apenas teremos que reimplementar a camada com base nos contratos da nova API. Do ponto de vista da aplicação principal, nada mudou.

O isolamento com serviços é de granularidade baixa, o que significa que não é necessário um mapeamento 1-para-1 entre o que precisamos que seja feito, e como a biblioteca de terceiros o faz. O isolamento lógico também acontece naturalmente , pois a interface indica o que queremos que aconteça sem explicitamente ter que dizer como a implementação tem que funcionar.

Isolando bibliotecas que abraçam o sistema

Nesta categoria o nosso código normalmente não chamada a biblioteca diretamente, mas depende de classes ou contratos nela. Este tipo de biblioteca também é conhecido como framework – em que se espera que implementemos alguma interface, herdemos alguma classe, ou usemos algum conjunto de anotações da biblioteca de terceiros. Ao mudar a biblioteca todos estes objetos terão que ser refeitos, ou reconfigurados, conforme uma nova biblioteca que venhamos a adotar. Por exemplo, um controlador, normalmente é um objeto anotado que o Spring utiliza para realizar as ações. Mas ao mudar para outra tecnologia, este controlador teria que ser escrito com as regras e convenções da nova biblioteca que podem ser completamente diferentes da que estamos usado.

A receita para este tipo de biblioteca que nos obriga a ter “pontos de entrada” que seguem os contratos da biblioteca é também isolar em uma camada esses objetos de “cola” entre a biblioteca e o resto do sistema. Estes objetos não devem ter nenhuma lógica que nos interesse e devem simplesmente delegar aos objetos nas camada abaixo, onde fica a lógica e as quais controlamos sem dependências de terceiros.

Repare que a solução é a mesma que antes, mas no caso anterior o sistema chama a camada isolada através de interfaces criadas e controladas pelo sistema. Aqui, a camada atua como uma fronteira que irá chamar o código do nosso sistema.

Isolando bibliotecas usadas em várias camadas

As bibliotecas que podemos usar em todas a camadas se distinguem entre bibliotecas utilitárias e bibliotecas de objetos de valor.

Bibliotecas de objetos de valor proveem algum tipo ou tipos de objetos, que usamos diretamente nos tipos dos campos e parâmetros de nossa aplicação. Por exemplo, o Joda Time, ou o moment.js.

Quando o Joda Time, por exemplo, se tornou relevante já havia um problema de como migrar do uso do Date/Calendar do SDK para o Joda Time. Era sabido que Date não era uma boa implementação do conceito e que Calendar não era um Objeto de Valor. Joda Time resolveu isso, e para sistemas criados depois, o uso era bem fácil, inclusive ganhando suporte de bibliotecas como o Hibernate. Mas recentemente o próprio SDK Java vem que com uma API melhor que a do Joda Time, e temos outra vez o mesmo problema de mudar tudo de novo. No próprio site do Joda Time, podemos ler

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) – a core part of the JDK which replaces this project.

– Joda Time, https://www.joda.org/joda-time/

Para este tipo de biblioteca de objetos de valor, em que vários objetos interagem, o padrão Adapter fica um pouco à quem das necessidades. Teríamos que partir para um padrão mais sofisticado como Bridge, que não apenas isolar os objetos em interfaces, mas mantém uma fábrica centralizada de todos eles. Ao mudar de biblioteca, apenas a fábrica é alterada. Isto acontece que não estamos tentando adaptar apenas uma única interface, mas um conjunto, que atuam juntos.

A complexidade aparente do padrão Bridge e falta de familiaridade com esse padrão leva as pessoas a abandonarem o conceito de isolamento e a aceitar usar uma biblioteca “pura” dentro da sua base de código. Quando essa biblioteca ficar obsoleta ou ganhar vulnerabilidades de segurança a única solução é mudar todo o código, o que, claramente, não é simples ou barato e na prática – ninguém faz.

Para as bibliotecas utilitárias, a forma de isolamento, vai depender muito do que elas fazem. Embora elas não estejam presentes como campos ou parâmetros, estão muitas vezes presentes dos algoritmos. Bibliotecas de log parecem bem inofensivas e temos até API no SDK, mas todos os projetos Java insistem em usar alguma biblioteca externa. Bibliotecas como lowdash/underscore foram muito úteis, mas hoje, a maior parte das operações podem ser feitas diretamente no objeto array do JavaScript. O mesmo pode ser dito de API como Guava e Apache Commons cujas funcionalidades vêm sendo absorvidas pelo SDK do Java.

Para este tipo de bibliotecas é preciso ter uma atenção especial e redobrada ao tempo de vida da aplicação. Realmente vai dar um pouco de trabalho isolar algumas coisas. Talvez o isolamento não vai ficar bom de primeira. Contudo, é aqui que mora o perigo. Estas são as bibliotecas que normalmente pensamos que nunca iremos mudar. Mas “nunca” não existe, e quando chega a hora, temos problemas sérios de débito técnico por que seu uso está pulverizado em toda a base de código. Nesse ponto seria necessária uma força-tarefa para mudar todo o código de uma vez, remover o problema e seguir em frente, mas é claro que isso nunca acontece. O resultado disso é uma obsolescência crescente do código fonte e portanto do sistema, até ao ponto onde tem que ser abandonado ou reescrito. Se o produto é de longa vida, não seria mais barato – embora que custoso – arrumar o problema antes ? A questão do custo presente e custo futuro permeia toda esta discussão.

Existem certas API que jogam um papel mais importante nas escritas das aplicações que outras. As que definem objetos de valor são as que mais impactam, e portanto as que mais deveríamos ter cuidado, e ainda assim, são as que menos damos importância, até que é tarde de mais.

Usando normalizações

No mundo Java é comum que muitos provedores implementem bibliotecas que competem entre si, mas que basicamente têm as mesmas funcionalidades. Então, em certas ocasiões isso leva a questões de normalização em que se pretende que haja uma padronização de contratos e comportamentos que depois cada vendedor pode implementar e estender como quiser.

Várias JSR têm este objetivo e vimos algumas soluções como JPA, JTA, JDBC , JCache, entre outras, que têm boa aceitação e uniformidade. O problemas é que ao tentar ser compatível com uma especificação, as funcionalidades são, muitas vezes, limitadas. O Hibernate, por exemplo, por ser usado como um provedor JPA, mas é mais útil se usado puro pois a especificação JPA é muito desfasada das reais necessidades dos sistemas.

Usar especificações padrão ajuda as aplicações Java com a questão de evitar se acoplar a uma biblioteca especifica de um vendedor específico. Se estava usando Hibernate e estava muito lento talvez quiera mudar para o EclipseLink. Isto só é possível porque o trabalho da JSR já isolou o contrato padrão e como ele deve funcionar.

Contudo isto nem sempre funciona. A especificação EJB 3 é completamente diferente e incompatível com a EJB 2. Aplicações criadas no principio do século usando a especificação de EJB 2 são hoje obsoletas- apenas 10 a 15 anos depois.

A API de logging padrão do Java não teve muita tração e bibliotecas como o SLF4J continuam sendo usadas, mesmo havendo um padrão no SDK.

Mesmo usando padrões e especificações que visam compatibilizar implementações de diferentes vendedores é sempre possível que essas especificações sejam limitadas ou que venham a ficam obsoletas.

Efetivamente ao se basear nestas especificações você não está criando vínculos com bibliotecas de terceiros, mas com a especificação em si. Os riscos disso são os mesmos listados no principio, especialmente o risco de Limitação. API de JPA original não tinha nenhuma forma de pesquisa (query) o que tornava a especificação, na prática, extremamente limitada e por isso, muita gente usava o Hibernate puro, diretamente.

Em outros ecossistemas, como o .NET, a normalização é feita centralmente de forma que já é provida a implementação padrão – e única – como parte do SDK. No ambiente .NET não há muita necessidade de isolar bibliotecas externas, porque não há tanta necessidade das usar. O próprio SDK já provê praticamente tudo que é necessário.

Poderíamos pensar em isolar o próprio SDK. Em algumas ocasiões isso seria uma boa ideia se o SDK é limitado ou mal desenhado – APIs de manipulação de datas e horas são bastante horríveis em várias plataformas – o problema aqui é reconhecer que o que é provido pelo SDK é mal desenhado. Isso vem com a experiência e o tempo. Também através de estudos comparativos entre plataformas e SDKs. Por exemplo, o .NET não conta com uma API como o java.time (JSR 310) e se quisermos trabalhar com datas e horas de uma forma correta, teremos que apelar para o Noda Time, por exemplo. O mesmo podemos dizer do JavaScript, em que o objeto Date tem todos os mesmos problemas que a sua contraparte no Java. A ideia de construir algo parecido com o Joda/Noda Time no Javascript está chegando um pouco tarde, mas é muito bem vinda. Contudo, quem usa APIs de terceiros irá ter um problema quando esta API temporal sair, se a sua API atual não estiver bem isolada.

Isolamento no nível empresarial

Quando a empresa tem vários produtos no seu portfólio e/ou constrói diversos softwares este problema de isolamento de bibliotecas de terceiros se multiplica. Cada software se torna um potencial vetor que todos os riscos que falamos no inicio. A governança fica mais difícil. Aqui a recomendação é que a empresa entenda e faça bem as contas de quanto risco quer tomar. Um Log4j espalhado por N aplicações significa N pontos vulneráveis a ataques. O reverso também pode ser um problema. Se todas as aplicações da empresa usam uma biblioteca interna a correta manutenção dela, se torna o gargalo. Contudo este é um problema de mais fácil solução que o primeiro, por que o controlo está com a empresa.

Empresas como a Fundação Apache , Google e outras, tratam os seus softwares como parte de uma única iniciativa e portanto todo o código externo é isolado. No caso destes exemplos ele é isolado da forma que todo o mundo deveria fazer : implementando toda as bibliotecas do zero ou usando apenas bibliotecas de muito baixo nível como manipuladores de byte code, por exemplo. Mas como já vimos, isso não é para o bolso e conhecimento de todos. Há uma fator relacionado com os skills da equipa que existe na sua empresa. O bom é que mesmo que esses skills sejam baixos, podem ser sempre aumentados: quer por treinamento, quer contratando pessoas com mais skills.

A empresas que optam por ter suas bibliotecas próprias imediatamente enfrentam o problema da manutenção. Então a solução é são colocá-las à disposição do público, utilizando o Open Source. Isto permite que :

  1. Mais pessoas usem a biblioteca e portanto encontrem bugs casos eles existam. Também permite coletar informação de usabilidade e ergonomia.
  2. As empresas distribuam a manutenção por todos os usuários das bibliotecas, que não precisam ser seus empregados. Pessoas utilizadoras da biblioteca têm interesse natural em que ela continue funcionando corretamente.

Esta estratégia de criar bibliotecas in-house que são depois disponibilizadas via open source, é o padrão de mercado. Se pensar um pouco, que maior parte da API de terceiros que você usam caem nesta categoria. As outras, são em si mesmas produtos.

Quando usamos bibliotecas de terceiros estamos utilizando a plataforma de outra empresa. Ou quiçá até misturando várias plataformas de outras empresas. Mas sem o devido isolamento, nunca teremos a nossa plataforma. Isto pode ser uma boa estratégia se o software que você está construindo não é feito para durar, mas se você está construindo um produto, é bem provável que ele precise continuar funcionando daqui a 5, 8, 10 anos , ou mais.

Se o software da empresa só depende de bibliotecas da empresa, não é relevante se elas são, ou não, implementadas recorrendo a bibliotecas de terceiros. Como vimos é uma questão de velocidade e aceleração. Ao criarmos nossos próprios contratos para o código que necessitamos já estamos fazendo a parte mais difícil do isolamento. A partir daí podemos implementar esses contratos usando bibliotecas de terceiros, e depois, com tempo, escrever as nossas. Ou simplesmente ir trocando a API de terceiros por uma melhor.

Criar e controlar o design das bibliotecas que usamos no software da empresa tem a vantagem que podemos oferecer um design mais simples, robusto ou seguro aos desenvolvedores da empresa de forma que no computo geral, o uso de bibliotecas internas passa a ser mais rápido que usar bibliotecas de terceiros. A governabilidade é também maior, pois podemos saber o que está sendo usado e que riscos existem.

Existe aqui uma oportunidade de um design mais adequado, há também um risco que tem que ver com a evolução do design em si mesmo.

Evolução de conceitos e isolamento

Quando o momment.js foi criado, a preocupação da comunidade com imutabilidade era quase nenhuma e poucas pessoas conheciam esse conceito ou sabiam como aplicá-lo. O mesmo pode ser dito do Calendar do Java que é modelado mais ou menos da mesma forma.
Avançamos um pouco no tempo e vemos que as API de hoje em dia têm muito mais preocupação com a imutabilidade e a fluência. Especialmente API que fornecem objetos de valor.

Podemos ver como as API evoluem de forma que acompanham as tendências do momento. Nem sempre isto acontece, mas quando acontece é um caso importante para a nossa tentativa de isolamento. Falamos que o isolamento é conseguido criando contratos e interfaces para os aplicar em código. Contudo, o estilo dessas interfaces afetará a aplicação para todo o sempre. Se o estilo é antiquado ou pouco ergonômico, será difícil escrever código usando essa biblioteca e os programadores ficaram tentados a culpar o isolamento por uma API menos bem desenhada.
É necessário entender que o ato de isolar depende de um ato de design , mas a falha em obter um bom design, não é uma falha em isolamento. Claro que gostaríamos dos dois, mas isolamento é algo mecânico, design de API depende de experiência , competência e imaginação.

Contudo, existem algumas características que ajudam a ter um bom design ao mesmo tempo que termos um bom isolamento:

  • Sempre definir os contratos usando interfaces (Interface Segregation Principle)
  • Nunca copie o contrato da API que irá usar como base da implementação. Estabeleça primeiro o que você precisa.
  • Sempre defina o menor número possível de método., Incremente conforme necessário.
  • Sempre faça a sua API o mais fortemente tipada possível.
    • Use Generics para propagar os tipos corretamente.
    • Use Tiny Types e evite usar tipos como String que não se adequam ao contexto e que servem para “conter tudo”.
  • Sempre torne a sua API o mais fluente possível. Não se aplica em todos os casos, mas nos que sim, faça-o.
  • Crie testes unitários da sua API que não dependem da biblioteca de terceiros usada na implementação; os resultados têm que ser os mesmos independentemente de qual biblioteca é usada.
  • Sempre pense que trocar uma API por outro deve implicar mudar uma linha simples em algum ponto. Desenhe com isso em consideração. Fábricas e Métodos Estáticos de Fábrica são ótimos para serem esses pontos.

Nem todas as linguagens estão igualmente equipadas para estes pontos, então tire partido da sua linguagem o mais possível.

Nem sempre o design mais adequado é óbvio. Pesquisas as várias API de mercado e realizar benchmarks comparativos do seu design pode ajudar a escolher um design mais adequado para os seus contratos.

Isolar é o mesmo que reinventar a roda ?

Não. É exatamente o oposto. É assumir que existem muitas opções de rodas e que temos que ter a capacidade de mudar entre elas conforme nos aprouver. Se a roda que estamos usado, quebra, ou não é mais eficiente, trocamos por outra. O que precisamos é um mecanismo que nos permita trocar uma pela outra sem alterar mais nada. Do mesmo jeito que os pneus são trocados numa box de Fórmula 1. Queremos trocar os pneus, mas a forma mais simples é trocar a roda toda. O contrato de montagem entre a roda e o carro é sempre o mesmo, independente do pneu. Isto é isolamento.

Queremos poder trocar de biblioteca alterando um único ponto do sistema, normalmente trocando uma implementação de uma interface por outra. Isto não é reinventar a roda, é alavancar o conhecimento que todas as rodas têm os mesmos conceitos – mesmo contrato – e desenhar para o contrato e não para uma roda especifica.

Eventualmente podemos construir nossa própria roda que se adequa ao contrato, mas esse não é o objetivo do isolamento, é uma consequência.

Contudo, porque o uso de bibliotecas de terceiros é, em essência, um débito técnico com vários riscos, deveríamos apenas usar bibliotecas de terceiros quando realmente não dominamos os algoritmos ou os detalhes de implementação. Quando sabemos implementar, deveríamos fazê-lo. O problema é que aqui há uma correlação com a a capacidade da equipes de desenvolvedores na empresa. Equipes menos experientes terão mais tendência a protelar e a usar bibliotecas de terceiros para todo e qualquer detalhe – mesmo até coisas óbvias. Equipes mais experientes tenderão ao contrário. É por isso que vemos as empresas como Goggle e a Fundação Apache ir pelo caminho de implementar tudo in-house. É realmente o certo, e o certo é simples quando a equipe é formada por profissionais experientes. Estas equipes são também experts em design o que nos leva ao ponto inicial. Em equipes menos preparadas, sempre haverá a tendência a fugir da responsabilidade de implementar o que quer que seja. As bibliotecas de terceiros são usadas diretamente e sem isolamento exatamente porque as equipes não tem conhecimento de design, não o cumprem, ou simplesmente não têm o skills técnicos para serem capazes de fazer o isolamento. Isso, contudo, não prova que o isolamento é desnecessário ou complexo. Não é. Apenas acontece que a equipe não é competente, naquele momento, para o conseguir.

Resumo

Isolar bibliotecas de terceiros é uma necessidade em aplicações de longa vida devido aos riscos que elas implicam. Mesmo quando isso não é aparentemente necessário. O isolamento não é uma tarefa difícil, mas muitos a consideram perda de tempo. Sempre compare o tempo de implementação com a vida estimada da aplicação. Duas semanas de trabalho pode ser muito tempo em uma aplicação que só vai ser usada uma vez, mas não é nada em uma aplicação feita para durar 10 anos. Se a aplicação em que está trabalhando não é de longa vida este artigo não se aplica.

Do ponto de vista do código, o isolamento é conceitualmente simples: use interfaces e o Principio de Segregação de Interfaces. Isto pode ser tão simples quando usar o padrão Adapter ou o padrão Service ou um pouco mais complexo como usar o padrão Bridge, mas sempre se trata de definir e implementar interfaces. Mantenha-se atento ao acoplamento lógico e tente seguir as regras do domínio do problema e não as regras que a biblioteca de terceiros usa.

Já que o contrato da interface é definido por si, isto é uma oportunidade para um melhor design, mais apto ao uso da equipe e da empresa e mais antenado com as tendências modernas de design de API e com as capacidades mais modernas da linguagem/ plataforma que estiver usando. Se não tem experiência com isto, mantenha o contrato o mais simples possível, com o menor numero de operações e conceitos possível.

Apenas use bibliotecas de terceiros quando realmente não souber como o algoritmo se implementa ou se a implementação, testes e detalhes forem demasiado complexos. Apenas, ocasionalmente utilize bibliotecas de terceiros para ganhar velocidade. Em ambos os casos, tenha sempre em mente que está perante um débito técnico. Desenhe em concordância com isso.

Comente

Scroll to Top