Subindo uma API REST em Clojure
Clojure é uma linguagem funcional e dinâmica que roda na JVM e que vem crescendo bastante no mercado de trabalho. Ela é tão performática quanto qualquer programa que roda em Java e muito menos verbosa. Isso facilita e muito na manutenção do código, influenciando no custo do desenvolvimento. Caso não esteja muito familiarizado com esta linguagem incrível, recomendo a leitura do livro Clojure for the Brave and True onde ensina desde o básico sobre Clojure.
Para testar algumas características da linguagem vamos criar uma aplicação com uma API REST básica. Esse projeto, o clojure7, que será um CRUD de posts de um blog.
Para quem programa ou já programou em Clojure, deve ter esbarrado em algum momento no Leiningen. Ele é um gerenciador de dependências e tasks, assim como o gradle e maven para os “javeiros”. Com o Leinigen, podemos construir a hierarquia do nosso novo projeto, rodar a aplicação com plugins, entre outros.
Dependências
Vamos utilizar um plugin chamado Compojure, para criar a hierarquia e arquivos básicos para nossa aplicação. O Compojure é uma biblioteca, não um framework, baseado no Ring (biblioteca que consegue manipular request e response, semelhante à especificação Servlet no Java). A diferença entre os dois está no fato do compojure permitir o gerenciamento das rotas da aplicação mais facilmente.
Caso tenha dúvidas de como o Ring funciona, sugiro a leitura de seus conceitos.
Vamos começar criando o projeto a partir do Leiningen:
$ lein new compojure clojure7
Ao rodar este comando, o lein irá criar alguns arquivos para nós:
- project.clj - arquivo de configuração do projeto, parecido com o
build.gradle
oupom.xml
- resources/ - pasta onde serão guardados arquivos como assets, templates html e migrações de banco
- src/ - pasta contendo todo o código clojure da aplicação
- test/ - pasta contendo os testes da aplicação
Vamos adicionar algumas dependências no arquivo project.clj
:
- ring-json - Permite lidarmos com requisições que contenham json e facilita as respostas da aplicação em json.
- korma - Uma das minhas libs favoritas em clojure, um ORM que mapeia nossos modelos clojure em modelos relacionais no banco de dados.
- mysql - Driver do mysql para clojure.
As dependências da aplicação ficarão assim:
:dependencies [[org.clojure/clojure "1.8.0"]
[compojure "1.5.2"]
[ring/ring-defaults "0.2.1"]
[ring/ring-json "0.4.0"]
[korma "0.4.3"]
[mysql/mysql-connector-java "5.1.6"]]
Ainda neste mesmo arquivo, podemos adicionar a configuração do ring
:
:ring {:handler clojure7.handler/app}
Na configuração acima temos um handler, um arquivo que deverá lidar com cada rota da aplicação, e o namespace apontando para o mesmo.
Ao abrirmos o src/clojure7/handler.clj
, vamos alterar o ring para realizar o bind dos parâmetros da request para json e dizer ao ring que após passar pela nossa rota, transforme os dados da resposta em json .
(ns clojure7.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.json :refer [wrap-json-response wrap-json-body]]))
(def app
(-> all-routes
wrap-json-response
wrap-json-body))
Neste mesmo arquivo, teremos as definições de rotas:
(defroutes all-routes
(GET "/" [] "Hello World")
(route/not-found "Página não encontrada :("))
Com o Compojure, temos a definição da nossa rota com o defroutes
, onde podemos passar uma lista de rotas para ele. Podemos ver que já temos uma rota GET para a raiz da aplicação que retornará uma String no corpo da resposta, e outra que caso não encontre nenhuma rota definida deste método, exiba a página de 404, “Página não encontrada :(”.
Banco de dados
Criaremos então a nossa tabela post
no nosso banco de dados mysql:
CREATE TABLE post (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`category` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
A título de curiosidade, assim como no Ruby on Rails, podemos ter migrações de banco de dados para facilitar na criação/alteração de novas tabelas. Neste post não irei demonstrar pela extensão do assunto, mas caso queira aprender, sugiro o uso do migratus que também é outra lib excelente.
Vamos criar o arquivo src/clojure7/db.clj
contendo as configurações do banco de dados:
(ns clojure7.db
(:use korma.db))
(defdb db (mysql
{ :classname "com.mysql.jdbc.Driver"
:subprotocol "mysql"
:subname "//localhost/clojure7"
:user "root"
:password ""}))
Recursos
Se tratando de REST, nosso modelo post
é um recurso, então vamos mapeá-lo no arquivo src/clojure7/post/post.clj
:
(ns clojure7.post.post
(:require [korma.db :refer :all]
[korma.core :refer :all]
[clojure7.db :refer :all]))
(defentity post)
Podemos observar que com o defentity
transformamos o objeto post
em uma entidade gerenciada pelo korma. Agora podemos manipular os dados da tabela através do clojure, então vamos criar os métodos para cada operação do CRUD:
(defn find-all []
(select post))
(defn find-by-id [id]
(select post
(where {:id id})
(limit 1)))
(defn create [name category]
(insert post
(values {:name name :category category})))
(defn update-by-id [id name category]
(update post
(set-fields {:name name :category category})
(where {:id id})))
(defn delete-by-id [id]
(delete post
(where {:id id})))
Criaremos agora as rotas para alterar nosso recurso post
no arquivo src/clojure7/handler.clj
e adicionaremos no namespace dele o link para o namespace post.clj
, assim conseguimos acessar os métodos daquele namespace:
(ns clojure7.handler
(:require [compojure.core :refer :all]
[compojure.route :as route]
[clojure7.post.post :as post]
[ring.middleware.json :refer [wrap-json-response wrap-json-body]]))
(defroutes all-routes
(GET "/posts" []
(post/find-all))
(POST "/posts" req
(let [name (get-in req [:body "name"])
category (get-in req [:body "category"])]
(post/create name category)))
(GET "/posts/:id" [id]
(post/find-by-id id))
(PUT "/posts/:id" req
(let [id (read-string (get-in req [:params :id]))
name (get-in req [:body "name"])
category (get-in req [:body "category"])]
(post/update-by-id id name category)
(post/find-by-id id)))
(DELETE "/posts/:id" [id]
(post/delete-by-id id)
(str "Deleted post " id))
(route/not-found "Not Found"))
Subindo a aplicação
Podemos agora subir nossa aplicação com o seguinte comando lein ring server
, por padrão ele irá subir na porta 3000.
Para executar cada uma das ações que criamos no código, seguem abaixo os comandos:
- Criando um
post
curl -X POST localhost:3000/posts -H "Content-Type: application/json" -d '{"name":"Clojure com o Simbal", "category":"cool-posts"}'
- Listando todos os
post
’s
curl -X GET localhost:3000/posts
- Editando um
post
curl -X PUT localhost:3000/posts/1 -H "Content-Type: application/json" -d '{"name":"Clojure com o Greg", "category":"other-posts"}'
- Encontrando um
post
curl -X GET localhost:3000/posts/1
- Removendo um
post
curl -X DELETE localhost:3000/posts/1
Gostou do post? Tem alguma dúvida? Alguma crítica? Comenta aqui em baixo :) Espero que tenha despertado, para quem não conhece, aquela curiosidade que todo dev tem quando descobre que existe outra maneira de subir uma API REST nos seus novos projetos :D