Como desenvolvi um backend web em Clojure
Tags: clojure, webdev, productivity, backend
Published: 2023-07-03 11:45:35

Um dos meus alunos do curso [Clojure: Introdução à Programação Funcional](https://cursos.segunda.tech) fez uma pergunta interessante:

>Olá professor, gostaria de informações sobre o deploy de uma aplicação, com um frontend enviando solicitações e obtendo repostas (como no caso do carrinho de compras, por exemplo). Estou gostando do curso, porém acho que uma construção mínima com um front-end ou implementado um service nos aproximaria mais do uso "no mundo real".

Embora a pergunta fuja bastante do escopo do curso (cujo foco são os conceitos do paradigma funcional - a linguagem em si é um detalhe), é uma dúvida bastante compreensível. É natural querer saber como aplicar os conhecimentos na prática.

Respondi o aluno, mas resolvi criar este artigo para poder explicar de forma mais completa. Não irei mostrar o passo a passo de como criar um serviço REST em Clojure, mas vou dar alguns caminhos possíveis, visando facilitar a trajetória daquelas pessoas interessadas neste tema.

Ao longo do artigo mostro como foi a minha linha de raciocínio para implementar um backend REST em Clojure que comecei a desenvolver ano passado e que continuo sustentando e adicionando funcionalidades.

# _Single Page Application_ ou _Server Side_?

Uma das decisões que precisei tomar logo no início foi se iria renderizar o HTML no backend e utilizar um framework Clojure **fullstack** ou se iria implementar um SPA, utilizando o Clojure apenas no backend (através de serviços REST).

Clojure permite as duas abordagens. E ao escolher desenvolver um SPA, você pode até mesmo utilizar Clojure para implementar também o frontend, utilizando [ClojureScript](https://clojurescript.org)!

Mas optei por utilizar Clojure apenas no backend e desenvolvi o frontend utilizando minha linguagem de programação favorita: [Elm](https://elm-lang.org).

Queria utilizar apenas linguagens funcionais em toda a _stack_ e como (pelo menos por enquanto) Elm é uma linguagem focada no desenvolvimento de frontends, precisava de outra linguagem para o backend. Como já queria explorar o uso do framework [Elm Land](https://elm.land) e a biblioteca [Elm UI](https://github.com/mdgriffith/elm-ui), optei por criar um SPA em Elm e utilizar Clojure apenas no backend.

Mas é possível implementar toda a solução em Clojure, caso você ache interessante.

# Frameworks ou bibliotecas?

Em muitas linguagens é comum existirem **frameworks** web que agregam diversas funcionalidades. Quem já programou em Java provavelmente já estudou sobre o [Spring](https://spring.io) ou [Quarkus](https://quarkus.io). Já em Ruby temos o famoso Ruby on Rails, no PHP existem soluções como [Laravel](https://laravel.com) e [Symfony](https://symfony.com) (ou ainda o famigerado Wordpress), entre tantos outros possíveis exemplos. Mas existem também muitas **bibliotecas**.

É difícil segmentar e definir o que faz com que uma tecnologia seja considerada um frameworks ou uma biblioteca, e não vou tentar fazer isso aqui. Para este artigo, basta considerar que enquanto um framework tenta resolver diversos tipos diferentes de problemas, uma biblioteca costuma ter um escopo bem mais reduzido. Desta forma, em geral, é necessário compor um conjunto de bibliotecas para solucionar um problema maior.

Como exemplos de bibliotecas para o universo web, no Ruby poderia citar o [Sinatra](https://sinatrarb.com), para Java/Kotlin o [Spark](https://sparkjava.com), entre muitos outros.

E como você deve imaginar, existem também frameworks e bibliotecas para desenvolvimento web em Clojure. E um framework famoso é o **Luminus**.

# Luminus

O [Luminus](https://luminusweb.com) se classifica como um **micro-framework Clojure, baseado em um conjunto de bibliotecas leves**. Esta é uma característica relativamente comum entre os frameworks web: juntar diversas pequenas bibliotecas de forma coesa e bem integrada que, juntas, possuem tudo que normalmente é necessário para implementar uma solução web completa.

Vale muito a pena conhecer este projeto e ver o que é possível fazer em Clojure. Com ele, você poderá criar facilmente um projeto muito bem organizado e com todas as funcionalidades esperadas atualmente, incluindo um sistema de [migrations](https://luminusweb.com/docs/migrations.html) do schema do banco de dados, testes automatizados, configurações separadas por ambientes, [algumas facilidades para interagir com banco de dados](https://luminusweb.com/docs/guestbook#accessing_the_database), etc.

Mas, embora muito bacana, me parece que esta não é uma abordagem tão popular dentro da comunidade Clojure. O que tenho observado é que, em geral, as pessoas desenvolvedoras Clojure costumam preferir escolher algumas bibliotecas e montar o seu próprio framework, de acordo com suas necessidades.

E este também foi o caminho que escolhi seguir: criar o meu próprio framework customizado para as necessidades do meu projeto. Em parte, segui este caminho pois o que precisava construir não era algo tão complexo assim. Aproveitei então para me aventurar nas diversas bibliotecas do mundo Clojure!

# Selecionando as bibliotecas

## Automação e gestão de dependências

Escolhi o [Leiningen](https://leiningen.org) para gerenciar as dependências do projeto e automatizar tarefas como executar testes automatizados, gerar a _build_ (_Uberjar_) do projeto, executar o terminal do REPL (_Read-Eval-Print Loop_), etc.

Se você vem do mundo Java, pense no Leiningen como sendo uma alternativa ao [Apache Maven](https://maven.apache.org) ou [Gradle](https://gradle.org).

Existem outras ferramentas similares, mas o Leiningen parece ser a mais utilizada no universo Clojure e atendeu bem minhas necessidades.

## Servidor HTTP

Para desenvolver uma aplicação web é necessário, claro, ter um servidor para receber as requisições. Para isso, escolhi a biblioteca [Ring](https://github.com/ring-clojure/ring).

Para quem conhece Python, pode pensar que Ring seria algo equivalente ao [WSGI (Web Server Gateway Interface)](https://docs.python.org/3/library/wsgiref.html) e o pessoal acostumado com Ruby pode associá-la ao [Rack](https://github.com/rack/rack).

Ring é uma biblioteca simples, mas de baixo nível de abstração. Por isso pode ser um pouco complicada de configurar. Optei então por utilizar a biblioteca [Ring-Defaults](https://github.com/ring-clojure/ring-defaults), que tem como objetivo prover uma configuração inicial segura e que atenda a maioria dos casos. Com ela foi muito fácil configurar o Ring e criar meus primeiros serviços REST.

## Gerenciador de rotas

Ring é tão enchuta que não conta nem mesmo com um sistema de gerenciamento de rotas! Então precisei acrescentar uma biblioteca para esta finalidade.

Para isso, escolhi utilizar a [Compojure](https://github.com/weavejester/compojure). Trata-se de uma biblioteca bastante simples, que acrescenta algumas facilidades ao Ring relacionadas à criação de rotas.

Criar um _endpoint_ `/ola-mundo` utilizando a Compojure é bastante fácil:

```Clojure
(defroutes app
  (GET "/ola-mundo" [] "<h1>Olá Mundo</h1>"))

```

Mas claro que para serviços mais complexos você pode (e deve) delegar o processamento das requisições para outras funções.

## Conectando ao banco de dados (PostgreSQL)

Com as bibliotecas listadas acima, já é possível criar um servidor simples e retornar as requisições. Mas a maioria dos projetos vai precisar mais do que isso.

Uma necessidade comum é se comunicar com um banco de dados. No meu caso, precisava acessar um banco PostgreSQL.

Optei por utilizar a biblioteca [clojure.java.jdbc](https://github.com/clojure/java.jdbc). Mas note que, embora esta biblioteca ainda seja mantida (o autor continue aplicacando _patchs_ de correções), ela está descrita no site como sendo _"Estável" (não mais "Ativa")_. O site ainda recomenda que em seu lugar seja utilizada a [next-jdbc](https://github.com/seancorfield/next-jdbc).

Portando, em um projeto novo, eu daria prioridade à esta outra biblioteca (e está em meus planos migrar o projeto para ela no futuro).

### PostgreSQL

A biblioteca [clojure.java.jdbc](https://github.com/clojure/java.jdbc) é agnostica e pode ser usada em qualquer banco de dados com suporte ao JDBC (_Java Database Connectivity_) e para conseguir me conectar ao banco PostgreSQL, também precisei utilizar a [org.postgresql/postgresql](https://clojars.org/postgresql).

### Pool de conexões

As duas bibliotecas que citei acima são suficientes para conectar e executar _queries_ no banco mas, por questões de performance, é importante utilizar um _pool_ de conexões. Para isso utilizei e recomendo a [hikari-cp](https://github.com/tomekw/hikari-cp). Já utilizava ela quando programava em Java e sempre me atendeu muito bem.

## Criando as _queries_

Pessoas que estão acostumadas com Java, C# e similares, devem estar habituadas a utilizar ferramentas de ORM (_Object Relational Mapper_) como [Hibernate](https://hibernate.org) ou [Entity Framework](https://learn.microsoft.com/pt-br/ef/). Mas dentro da comunidade Clojure, não vejo este tipo de ferramenta sendo muito utilizada. No contexto de Clojure, é mais natural que uma chamada ao banco retorne uma estrutura da dados, como um mapa.

Optei por escrever o código SQL manualmente em algumas poucas _queries_ mais complexas, mas para a maioria dos casos utilizei a biblioteca [Honey SQL](https://github.com/seancorfield/honeysql). Com ela é possível construir as _queries_ como se fossem estruturas de dados Clojure.

Um exemplo seria:

```Clojure
(defn query-select-auditoria-xpto-por-cpf
  [cpf]
  (-> (h/select :created_at
                :remote_address
                [[:raw "request_headers->'x-alias'"] :alias])
      (h/from :auditoria)
      (h/where [:= :cpf cpf]
               [:= :path "/api/xpto/"]
               [:= :response_status 200]
               [:= :method "GET"])
      (h/order-by [:id :desc])
      sql/format))

```

Note que esta função **não executa** a _query_, apenas gera o comando SQL, sendo portanto uma [função pura](https://www.youtube.com/watch?v=QenAR_3XtXU). Ao executá-la, o seu resultado será:

```Clojure
(query-select-auditoria-xpto-por-cpf "123.345.789-01")

;; Resultado:
["SELECT created_at, remote_address, request_headers->'x-alias' AS alias FROM auditoria WHERE (cpf = ?) AND (path = ?) AND (response_status = ?) AND (method = ?) ORDER BY id DESC" "123.345.789-01" "/api/xpto/" 200 "GET"]

```

Para processá-la de fato, é necessário chamar a função `jdbc/with-db-connection` da biblioteca [clojure.java.jdbc](https://github.com/clojure/java.jdbc), passando a _query_ que deseja executar como parâmetro. Antes disso, claro, precisa configurar usuário, senha e endereço do banco, além do _pool_ de conexões. Mas isso já foge do escopo deste artigo.

## JSON

O padrão JSON é utilizado com muita frequência em projetos web. Para facilitar o _parsing_ (e geração) deste formato, utilizo a biblioteca [data.json](https://github.com/clojure/data.json).

Seu uso é bastante simples:

```Clojure
(json/write-str {:a 1 :b 2})
;; Resultado:
"{\"a\":1,\"b\":2}"

(json/read-str "{\"a\":1,\"b\":2}")
;; Resultado:
{"a" 1, "b" 2}

```

## Executando chamadas HTTP

Além de receber requisições HTTP, eu precisava me integrar com outros sistemas através de serviços REST (outra necessidade bastante comum em aplicações web distribuídas).

Para isso utilizei a biblioteca [clj-http](https://github.com/dakrone/clj-http) e seu uso é bastante simples. Para fazer uma chamada GET, basta executar algo como:

```Clojure
(client/get "https://exemplo.com/recurso/1234" {:accept :json})

```

## Caching

O sistema que desenvolvi executa algumas _queries_ pesadas no banco, para geração de relatórios. Mas embora o processamento seja oneroso, o resultado final não é um volume grande de dados e não precisa estar totalmente atualizado o tempo todo. Por isso, algumas destas operações podem ser cacheadas em memória.

Para isso, utilizei a biblioteca [clojure.core.cache](https://github.com/clojure/core.cache).

Embora esta biblioteca funcione bem, achei a sua interface um pouco confusa. Para contornar isso criei a minha própria abstração. Depois de alguns ajustes, consegui criar algumas funções que, embora escondam algumas funções mais avançadas da biblioteca, tornam a utilização do cache bastante simples.

## Chamadas assíncronas

Em um determinado momento precisei fazer uma integração com o chat do Microsoft Teams (através de uma chamada HTTP). Como já mencionei acima, utilizei a [clj-http](https://github.com/dakrone/clj-http) para isso. Mas queria que esta parte do sistema fosse assíncrona (não bloqueante). Para isso utilizei a biblioteca [core.async](https://github.com/clojure/core.async).

Para quem conhece a linguagem Go, esta biblioteca funciona de forma parecida com as famosas _Goroutines_. Ou seja, é uma _lightweight thread_.

Tornar uma função bloqueante em não-bloqueante é muito fácil: basta passar as expressões/funções que deseja executar em paralelo para a função `async/go`. No caso de uma chamada HTTP, seria algo como:

```Clojure
(async/go
  (try
    (client/post url-webhook-teams
                 {:body (json/write-str {"text" "Uma mensagem para o MS Teams"})
                  :headers {"content-type" "application/json"}
                  :socket-timeout 3000      
                  :connection-timeout 3000})
    (catch Exception e
      (println (str "Falha ao tentar enviar mensagem para o Teams: " e)))

```

## Agendamento de execuções (_scheduler_)

Outra necessidade comum de uma aplicação web, e que também precisei fazer, é o agendamento de execução de tarefas que devem ser executadas de forma periódicas (no meu caso, executar uma função a cada 10 minutos). Para esta finalidade, utilizei a biblioteca [Chime](https://github.com/jarohen/chime).

Embora seja relativamente simples, assim como ocorreu com a biblioteca de cache, inicialmente tive um pouco de dificuldade para entender como utilizá-la. Não é nada muito complexo, mas achei a documentação um pouco confusa. Novamente consegui isolar esta parte do código em uma função mais específica e deu tudo certo. Não tive mais problemas com ela.

# Mocks e testes automatizados

Além do código de produção, queria criar testes automatizados.

Nos primeiros serviços optei por implementar, além dos testes unitários mais básicos, vários testes integrados. Para isso utilizei a biblioteca [Ring-Mock](https://github.com/ring-clojure/ring-mock) para _mockar_ o servidor web. Também criei um dublê da função que fazia acesso ao banco de dados.

Com o tempo comecei a ter dificuldade para manter estes testes, que quebravam de forma não muito trivial. Outro grande problema era que, ao tentar praticar TDD, senti falta de um _type system_ para criar os _asserts_.

Às vezes criava todo o cenário de ponta a ponta, mas em _runtime_, quando estava executando a aplicação e me conectando no banco real, o tipo retornado era diferente do que eu estava imaginando. E mais uma vez encontrar exatamente onde estava o problema era um pouco chato às vezes. Nem sempre a mensagem de erro era realmente significativa.

Com o tempo optei por minimizar (pelo menos por enquanto) este tipo de teste. Tento maximizar a quantidade de funções puras e foco meus testes nelas. Sinto falta de testar algumas camadas da aplicação, sendo algo que ainda preciso experimentar e explorar mais.

# Separando as funções puras

Não quero entrar muito em detalhes da arquitetura do sistema neste artigo, mas ela está bastante simples. Cada caso de uso (serviço) tem a sua própria pasta, e as funções puras e impuras estão em _namespaces_ distintos. Me esforço para tentar transformar funções impuras em puras e tornar aquelas impuras em apenas orquestradoras.

As funções que causam algum tipo de efeito colateral devem ser o mais "burras" possível. Exemplo: uma função que executa uma _query_ no banco não pode ter **nenhuma** regra de negócio. O mesmo para uma função que executa uma chamada HTTP, etc.

Já considerava isso uma boa prática quando programava em outras linguagens, mas em Clojure (por ser funcional), torna-se muito fácil e natural fazer isso.

Com o tempo, acabei diminuindo e até eliminando alguns testes integrados e aumentando a quantidade de testes unitários. Para o meu contexto, isso funcionou bem, mas às vezes ainda sinto falta de testes mais abrangentes.

# Injeção de dependências

Pessoas acostumadas com Java, .NET e similares talvez queiram buscar alguma biblioteca de injeção de dependência para poder praticar inversão de controle.

Para atingir este objetivo em Clojure, tudo que precisei até agora foi passar referências de funções como parâmetros. Se uma função precisa, por exemplo, executar uma _query_ no banco de dados, eu passo como parâmetro a referência para a função que, ao receber um comando SQL, o executa e retorna o resultado. Faço o mesmo para chamadas HTTP ou outras formas de efeitos colaterais.

No contexto da minha aplicação, isso foi suficiente. Caso precise criar um dublê (_mock_/_spy_/_stub_), basta passar a referência de outra função (usada apenas para testes) como parâmetro. Assim consigo evitar o acoplamento com camadas externas (como banco de dados ou outros sistemas) nos testes, permitindo que tenha total controle sobre o que será retornado nessas chamadas, permitindo criar _asserts_ facilmente, que não irão quebrar com o tempo. Isso permite também que meus testes possam ser executados de forma concorrente, além de serem muito rápidos.

# Onde hospedar

Uma das grandes vantagens da linguagem Clojure - e um dos principais motivos de tê-la escolhido - é o fato de ser uma linguagem que roda na JVM (Java Virtual Machine). O resultado final da aplicação é um _Uberjar_ (um arquivo _jar_ com sufixo _-standalone.jar_) contendo toda a aplicação e dependências do projeto.

E o servidor criado da forma como descrevi neste artigo pode ser iniciado no ambiente produtivo com a seguinte linha de comando:

```
java -jar target/nome-projeto-0.0.1-SNAPSHOT-standalone.jar

```

No meu caso, estou utilizando uma nuvem privada _on-premise_. E um ponto interessante é que esta nuvem já tinha suporte à plataforma Java e eu já havia desenvolvido muitas soluções nesta linguagem. Migrar para o Clojure foi trivial, não precisando fazer nenhuma alteração na infraestrutura. Para o time do centro de dados, é totalmente transparente.

Ou seja, aplicações desenvolvidas da forma como descrevi neste artigo podem ser hospedadas em qualquer servidor que tenha suporte ao Java! 🎉

# Bônus: atualizando as dependências

Uma preocupação que sempre tenho em todo projeto é evitar o seu envelhecimento. É um processo natural de toda aplicação, que precisa de atenção constânte do time. E pelo menos para alguns destes tipos de problemas, o processo pode ser automatizado!

O [Leiningen](https://leiningen.org) possui um comando que verifica se existem dependências desatualizadas e, caso encontre alguma, é capaz de atualizá-la e em seguida executar os testes automatizados. Caso algum teste falhe, a alteração é desfeita e uma mensagem de erro é exibida indicando o problema.

Esta funcionalidade me ajuda diariamente! Criei um script com o comando e normalmente começo meu dia executando-o para garantir que o projeto está com as dependências sempre atualizadas.

O comando em questão é:

```
lein ancient upgrade :interactive :check-clojure

```

Mas para poder executá-lo é necessário configurar o _plugin_ [lein-ancient](https://github.com/xsc/lein-ancient).

# Meu _project.clj_ atual

A configuração de um projeto que utilize o Leiningen é toda feita no arquivo `project.clj`.

Abaixo compartilho como está a configuração do meu projeto no momento em que estou escrevendo este texto. Além das bibliotecas que aprensentei aqui, utilizo algumas outras configurações que não achei relevantes o suficiente para destacar, mas que podem ser úteis para algumas pessoas.

```Clojure
(defproject nome-projeto "1.0.0-SNAPSHOT"
  :description "Aqui ficaria a descrição do projeto."
  :url "https://endereço-repositorio-git"
  :min-lein-version "2.10.0"
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [compojure "1.7.0"]
                 [ring/ring-defaults "0.3.4"]
                 [com.github.seancorfield/honeysql "2.4.1045"]
                 [org.clojure/java.jdbc "0.7.12"]
                 [org.postgresql/postgresql "42.6.0"]
                 [hikari-cp "3.0.1"]
                 [org.clojure/data.json "2.4.0"]
                 [org.slf4j/slf4j-simple "2.0.7"]
                 [clj-http "3.12.3"]
                 [org.clojure/core.cache "1.0.225"]
                 [jarohen/chime "0.3.3"]
                 [org.clojure/core.async "1.6.673"]]
  :plugins [[lein-ring "0.12.6"]
            [lein-ancient "0.7.0"]
            [lein-cloverage "1.2.2"]]
  :ring {:handler um.namespace.qualquer.routes/app}
  :repl-options {:init-ns um.namespace.qualquer.logic}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.4.0"]]}})

```

# Conclusões

Clojure, em conjunto com as bibliotecas que mostrei ao longo deste artigo, me atenderam muito bem. Consegui criar uma arquitetura que é ao mesmo tempo simples e fácil de incluir novos serviços. O servidor consome poucos recursos e tem uma performance muito boa.

A escolha por criar o meu próprio framework, selecionando cada biblioteca isoladamente, inicialmente consumiu certo tempo. Tive que testar cada uma delas, descartar as que não atendiam minhas necessidades e construir alguns códigos para "colar" essas bibliotecas. 

Usar um framework pronto provavelmente iria aumentar a minha produtividade nas primeiras iterações, mas como é um projeto que espero manter por muitos anos, ter total controle sobre cada parte dele provavelmente é o cenário ideal.

A comunidade Clojure parece ter uma cultura de criar bibliotecas pequenas, com propósitos muito bem definidos. Acredito que esta característica, em conjunto com o fato de Clojure ser uma linguagem funcional, facilitou muito este processo. Essas duas características somadas permitiram que, apenas utilizando composição de funções, eu fosse capaz de criar grandes abstrações, sem adicionar muita complexidade.

Mas é importante deixar claro que este é um projeto pequeno, onde não precisei utilizar nenhuma arquitetura muito avançada. Não abordei nada como _Domain-driven design_, Arquitetura Hexagonal, Microsserviços, ... Então não consigo afirmar que Clojure (ou mesmo o paradigma funcional) seria uma boa opção para cenários mais complexos. Mas, até o momento, não estou arrependido da escolha e, se depender de mim, não volto a programar em uma linguagem orientada a objetos novamente tão cedo! 😇

---

E você? Quais tecnologias têm utilizado para desenvolvimento web? Conte um pouco das suas experiências mais recentes nos comentários!

---

Gostou deste texto? Conheça meus outros artigos, podcasts e vídeos acessando: https://segunda.tech.

E se quiser, me segue lá no [twitter](https://twitter.com/marciofrayze) e [blue sky](https://staging.bsky.app/profile/segunda.tech).