Como os Frameworks abstraem isso?
Quando utilizei o Spring framework pela primeira vez fiquei curioso quando descobri que as classes criadas através dele (Beans) são, por padrão, singleton. Ou seja, a mesma instância de um @Controller por exemplo, é utilizada em toda requisição feita para a url em que ela está mapeada. Minha curiosidade era em saber como ele lidava com o estado dos atributos desses objetos. Em um bean podemos injetar outros objetos como um EntityManager e nesse caso os valores injetados em uma requisição não devem se misturar com os valores de outra. Devem ser Thread-Safe.
Graças a internet consegui encontrar alguém que teve a mesma curiosidade, foi estudar o código-fonte do Spring e escreveu um post sobre que você pode acessar aqui. O artigo tem o foco na utilização do @Repository, mas o princípio é o mesmo.
Em resumo, o Spring cria um proxy para cada Bean criado (Que pode ser um @Service, @Repository ou qualquer outro tipo) e internamente ele salva as propriedades que devem ser Thread Safe em um objeto ThreadLocal. Normalmente objetos com a anotação @PersistenceContext não devem existir em 2 threads(Requisições) diferentes. Então, quando existir uma propriedade com essa anotação em um fonte, o que acessamos é apenas um proxy que dará acesso ao valor pertencente àquela thread. Dessa forma não ocorrerá a situação de um EntityManager existir em duas requisições simultâneas.
Um exemplo bem simples da utilização de um ThreadLocal
O Microframework Javalin
Quando se trata de microframeworks, existem algumas opções que sempre vem a tona como o Vert.x e Spark (Não o Apache Spark). Porém, o microframework que tive contato foi o Javalin, mesmo não sendo o mais completo, possui uma curva de aprendizado muito baixa, é compatível com Kotlin, tem suporte a Websocket e Server Sent Events (SSE), e é extremamente leve.
Dependências necessárias para utilizá-lo
Um simples exemplo esperando uma requisição na porta 7000
Como o Javalin funciona
Quando se cria um novo servidor com o Javalin, é possível usar os chamados Handlers (Semelhante a um callback, listener, observable, etc) que serão responsáveis por tratar as requisições http. Os Handlers geralmente são interfaces de apenas um método com um paramêtro do tipo Context (que é um facilitador de acesso aos métodos da classe HttpServletRequest e HttpServletResponse. Sim, o javalin utiliza a api do javax.servlet e sua implementação é o Jetty). Abaixo um exemplo de como utilizá-los.
O problema com as conexões
Da primeira vez que utilizei o Javalin eu precisava lidar com o operações com banco de dados. Na época eu não optei por nenhuma biblioteca de ORM para facilitar o acesso aos dados ou de injeção de dependência para outros aspectos. O resultado disso foi muito código chato e repetitivo para tratar acesso ao banco e transações como os abaixo:
A situação era ainda pior quando precisava implementar algo muito extenso e com várias etapas. Como um método depende do resultado do outro, para manter tudo na mesma transação era preciso passar a referência da conexão que está sendo usada. Como no exemplo seguinte:
O que poderia ser feito para evitar esse tipo de código?
Depois de entender a premissa de como o Spring trata seus beans em um ambiente multithread, podemos usar as funcionalidades que o próprio Javalin disponibiliza para chegar em um resultado semelhante.
Com o objetivo de usar uma conexão por thread, podemos abstrair a criação, fechamento e rollbacks das conexões no ciclo de vida das requisições do Javalin. Dessa forma, podemos nos concentrar somente na lógica dos métodos de negócio sem a necessidade de passar a referência para todos os métodos que dependem de outro.
Mas, por onde começar?
Vinculando uma conexão do banco de dados a uma thread (que é criada para cada requisição feito por um client). E para isso vamos utilizar a classe ThreadLocal para nos ajudar.
- Abaixo temos a classe que será responsável por vincular uma conexão a uma thread. Dessa forma não precisamos nos preocupar com thread-safety na camada de acesso a dados.
- Utilizei o hikari CP para o pool de conexões com a base de dados. O arquivo .properties está disponível no repositório do github.
Agora com os handlers que conversam com o ciclo de vida da requisição, é possíveis criar, fechar e desfazer as alterações feitas durante aquela requisição.
-
Um handler para abrir a conexão a cada requisição
-
Um Handler para commitar e fechar a conexão após cada requisição
-
Um handler que irá fazer rollback na transação caso uma exceção especifica seja lançada. (Poderia ser qualquer tipo de exceção)
Para testar tudo isso, vamos fazer um cadastro simples
-
Uma entidade simples
-
Um service para acessar os dados
-
Um controller que chamará o service
-
Por fim a classe principal que inicializa tudo. Como não precisamos nos preocupar com threads em relação ao acesso a base de dados, os handlers podem usar sempre a mesma instancia do controlador
Conclusão
Esse foi apenas uma demonstração de como é possível utilizar apenas o que o Javalin nos dá para resolver o problema relacionado a gerência de transações/conexões. Claro que poderia ser mais sofisticado utilizando um framework de injeção de dependência ou ORM, mas não era o objetivo do artigo. O código pode ser acessado neste repositório.
Referências
Top comments (0)