DEV Community

Jonilson Sousa
Jonilson Sousa

Posted on

3 1

Anotações Capítulo 8: Boundaries

  • Sempre precisamos utilizar módulos, códigos fornecidos por terceiros.
  • E devemos integrar de forma limpa esse código externo ao nosso.

O uso de códigos de terceiros

  • Há uma tensão natural entre o fornecedor de uma interface e seu usuário.

  • Os fornecedores de pacotes (aqui no sentido de módulos) e frameworks de outros fabricantes visam a uma maior aplicabilidade de modo que possam trabalhar com diversos ambientes e atender a um público maior.

  • Já os usuários desejam uma interface voltada para suas próprias necessidades.

  • Essa tensão pode causar problemas nos limites de nossos sistemas.

  • Exemplo: java.util.Map, é uma interface bastante ampla com diversas capacidades. O que nos dá flexibilidade, isso é útil, mas também pode ser uma desvantagem:

    • Nosso aplicativo pode construir um Map e passá-lo adiante.
    • E nosso objetivo talvez seja que nenhum dos recipientes de nosso Map não exclua nada do Map. Mas logo no início da lista dos métodos do Map está o método clear(). E assim qualquer usuário do Map pode apagá-lo.
    • Ou talvez, segundo a convenção que adotamos, o Map pode armazenar apenas certos tipos de objetos, mas ele não restringe os tipos que podemos armazenar, assim qualquer usuário pode adicionar itens de qualquer tipo.
    • Exemplo sensors: Se temos um Map de sensors da seguinte forma:
Map sensors = new HashMap();
Enter fullscreen mode Exit fullscreen mode

E quando alguma parte do código precisar acessar o Sensor, terá que fazer:

Sensor s = (Sensor) sensors.get(sensorId);
Enter fullscreen mode Exit fullscreen mode

E isso aparece várias vezes ao longo do código. E com isso o cliente fica com a responsabilidade de obter um Object do Map e atribuí-lo ao tipo certo. Apesar de funcionar, não é um código limpo. E esse código não explica muito bem o que ele faz.
E poderíamos melhorar a legibilidade com o uso de tipos genéricos, como:

Map<Sensor> sensors = new HashMap<Sensor>();
…
Sensor s = sensors.get(sensorId );
Enter fullscreen mode Exit fullscreen mode

Observação: O Map deveria ter a chave e o tipo do valor.

Porém, isso ainda não resolve o problema de que o Map<Sensor> oferece mais capacidade do que precisamos ou queremos.
Passar adiante pelo sistema uma instância de Map<Sensor> significa que haverá vários lugares para mexer se a interface Map mudar.

Uma forma limpa de usar o Map:

public class Sensors {
    private Map sensors = new HashMap();

    public Sensor getById(String id) {
        return (Sensor) sensors.get(id);
    }

    //snip
}
Enter fullscreen mode Exit fullscreen mode

Dessa forma a interface no limite (Map) está oculta, e é possível alterá-la causando muito pouco impacto no resto do aplicativo. Essa classe também pode forçar regras de modelo e de negócios.

  • Não estamos sugerindo para sempre usar dessa forma.

  • Mas é bom não passar os Maps ou qualquer outra interface num limite por todo o sistema.

  • Se for usar, mantenha numa classe ou próxima a uma família de classes em que ela possa ser usada. Evite retorná-la ou aceitá-la como parâmetro de APIs públicas.

Explorando e aprendendo sobre limites

  • Códigos de terceiros nos ajudam a obter mais funcionalidade em menos tempo.

  • Não é tarefa nossa testá-los, mas pode ser melhor para nós criar testes para os códigos externos que formos usar.

  • Isso quando não é claro como usar uma biblioteca de terceiros. Podemos gastar um tempo lendo a documentação e decidindo como usá-la. E então escreveremos nosso código para usar o código externo e vemos que não é o que achávamos. E poderíamos passar muito tempo depurando o código e tentando saber se o bug é no nosso código ou no deles.

  • Entender códigos de terceiros é difícil. Integrá-lo ao seu também é. Fazer ambos ao mesmo tempo dobra a dificuldade, e se adotássemos outra abordagem?

  • Se criarmos testes para explorar nosso conhecimento sobre ele. O que o Jim Newkirk chama isso de testes de aprendizagem.

  • Nesses testes, chamamos a API do código externo como faríamos ao usá-la em nosso aplicativo. Assim estaríamos controlando os experimentos que verificam nosso conhecimento daquela API.

  • O teste foca no que desejamos saber sobre a API.

Aprendendo sobre log4j

Para usar o pacote log4j do Apache, baixaremos o pacote e abriremos a documentação inicial.

  • Sem ler muito, criamos nosso primeiro teste:
@Test
public void testLogCreate() {
    Logger logger = Logger.getLogger("MyLogger");
    logger.info("hello");
}
Enter fullscreen mode Exit fullscreen mode
  • Quando executamos, temos um erro dizendo que precisamos de algo chamado Appender. Após ler um pouco mais, descobrimos o ConsoleAppender. Então criamos o ConsoleAppender:
@Test
public void testLogAddAppender() {
    Logger logger = Logger.getLogger("MyLogger");
    ConsoleAppender appender = new ConsoleAppender();
    logger.addAppender(appender);
    logger.info("hello");
}
Enter fullscreen mode Exit fullscreen mode
  • Agora descobrimos que o Appender não possui fluxo de saída. Depois de pesquisar tentamos o seguinte:
@Test
public void testLogAddAppender() {
    Logger logger = Logger.getLogger("MyLogger");
    logger.removeAllAppenders();
    logger.addAppender(new ConsoleAppender(
    new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT));
    logger.info("hello");
}
Enter fullscreen mode Exit fullscreen mode
  • E funcionou. Uma mensagem “hello” apareceu no console! Porém: Parece estranho ter que dizer ao ConsoleAppender o que ele precisa escrever no console, e mais interessante é quando removemos o parâmetro ConsoleAppender.SYSTEM_OUT e ainda é exibido “hello”. Mas quando retiramos o PatternLayout temos a mensagem de falta de fluxo de saída. Lendo a documentação novamente, vimos que o construtor ConsoleAppender padrão vem “desconfigurado”, o que não parece óbvio ou prático. Parece um bug. Lendo mais detalhadamente sobre o pacote chegamos a seguinte série de testes de unidade:
public class LogTest {
    private Logger logger;

    @Before
    public void initialize() {
        logger = Logger.getLogger("logger");
        logger.removeAllAppenders();
        Logger.getRootLogger().removeAllAppenders();
    }

    @Test
    public void basicLogger() {
        BasicConfigurator.configure();
        logger.info("basicLogger");
    }

    @Test
    public void addAppenderWithStream() {
        logger.addAppender(new ConsoleAppender(new PatternLayout("%p %t %m%n"), ConsoleAppender.SYSTEM_OUT));
        logger.info("addAppenderWithStream");
    }

    @Test
    public void addAppenderWithoutStream() {
        logger.addAppender(new ConsoleAppender(new PatternLayout("%p %t %m%n")));
        logger.info("addAppenderWithoutStream");
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora sabemos como obter um console simples e inicializado de logs, com isso podemos encapsular essa lógica nas classes de logs para que o resto do código fique isolado da interface limite do log4j.

Os testes de aprendizagem são melhores que de graça

  • Esses testes acabam não custando nada. Porque tivemos que aprender sobre a API mesmo, e escrever eles foi uma forma fácil de aprender.

  • “Os testes de aprendizagem são experimentos precisos que ajudam a aumentar nosso entendimento”.

  • Esses testes também geram um retorno positivo. Quando houver novas versões daquele pacote, podemos executar os testes para ver se há diferenças nas atividades.

  • Cada distribuição vem com um novo risco. Porém com os testes de aprendizagem podemos saber se o pacote ficou incompatível com os testes.

  • Também pode ser mais fácil a atualização com os testes, assim podemos ir para novas versões sem grandes problemas desde que os testes continuem passando.

O uso de código que não existe ainda

  • Outro tipo de limite, que separa o conhecido do desconhecido.
  • Às vezes, o que está do outro lado nos é desconhecido. Às vezes, optamos por não olhar além do limite.
  • Pense num caso onde você vai ter que utilizar uma API externa mas que você não conhece ela e não sabe como ela funciona:
    • Digamos que você usará um subsistema chamado “Transmissor”.
    • Nesse caso a melhor abordagem é criar nossa própria interface “Transmitter” que será responsável por se comunicar com o subsistema "Transmissor".
    • Nela podemos criar um método “transmit” que pega a frequência e o fluxo de dados. Nesse caso, essa é a interface que gostaríamos que o subsistema "Transmissor" tivesse, que não sabemos como realmente é a sua interface.
    • Assim deixamos o código do nosso lado organizado e centralizado.
    • E podemos até utilizar testes com um “Transmissor fake” para testarmos o funcionamento do nosso lado.
    • Bem como usar “Adapter Transmitter” para fazer a comunicação no futuro com a API externa.

Limites limpos

  • Coisas interessantes ocorrem nos limites. A alteração é uma delas.
  • Bons projetos de software acomodam modificações sem muito investimento ou trabalho.
  • Quando usamos códigos que estão fora de controle, devemos dar uma atenção ao nosso investimento e garantir que uma mudança futura não seja muito custosa.
  • O código nos limites precisa de uma divisão clara e testes que definem o que se deve esperar. Evitar que grande parte do nosso código enxergue as particularidades de terceiros.
  • Melhor depender de algo que você controle do que pegar algo que acabe controlando você.
  • Lidamos com os limites de códigos externos através de alguns poucos lugares em nosso código que fazem referência a eles.
  • Podemos usar uma interface, ou um adapter para converter nossa interface perfeita na que nos for fornecida.
  • Nosso código se comunica melhor conosco, provê uso consistente interno e possui poucos pontos para serem mexidos quando o código externo mudar.

Image of Timescale

PostgreSQL for Agentic AI — Build Autonomous Apps on One Stack 1️⃣

pgai turns PostgreSQL into an AI-native database for building RAG pipelines and intelligent agents. Run vector search, embeddings, and LLMs—all in SQL

Build Today

Top comments (0)

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

If this post resonated with you, feel free to hit ❤️ or leave a quick comment to share your thoughts!

Okay