Construindo uma API com o Spark Java Framework
1. Introdução
Neste artigo, teremos uma introdução rápida aSpark framework. O Spark framework é um framework web de desenvolvimento rápido inspirado no framework Sinatra para Ruby e é construído em torno da filosofia Java 8 Lambda Expression, tornando-o menos detalhado do que a maioria dos aplicativos escritos em outros frameworks Java.
É uma boa escolha se você deseja ter uma experiência semelhante aNode.js ao desenvolver uma API da web ou microsserviços em Java. Com o Spark, você pode ter uma API REST pronta para servir JSON em menos de dez linhas de código.
Começaremos rapidamente com um exemplo "Hello World", seguido por uma API REST simples.
2. Dependências do Maven
2.1. Spark Framework
Inclua a seguinte dependência Maven em seupom.xml:
com.sparkjava
spark-core
2.5.4
Você pode encontrar a versão mais recente do Spark emMaven Central.
2.2. Biblioteca Gson
Em vários lugares do exemplo, usaremos a biblioteca Gson para operações JSON. Para incluir Gson em seu projeto, inclua esta dependência em seupom.xml:
com.google.code.gson
gson
2.8.0
Você pode encontrar a versão mais recente do Gson emMaven Central.
3. Primeiros passos com o Spark Framework
Vamos dar uma olhada nos blocos de construção básicos de um aplicativo Spark e demonstrar um serviço da web rápido.
3.1. Rotas
Os serviços da Web no Spark Java são construídos sobre rotas e seus manipuladores. Rotas são elementos essenciais no Spark. De acordo comdocumentation, cada rota é composta por três peças simples - averb, apath e acallback.
-
Overb é um método correspondente a um método HTTP. Os métodos verbais incluem:get, post, put, delete, head, trace, connect, eoptions
-
Opath (também chamado de padrão de rota) determina quais URI (s) a rota deve ouvir e fornecer uma resposta para
-
Ocallback é uma função de tratamento que é chamada para um determinado verbo e caminho para gerar e retornar uma resposta à solicitação HTTP correspondente. Um retorno de chamada aceita um objeto de solicitação e um objeto de resposta como argumentos
Aqui, mostramos a estrutura básica de uma rota que usa o verboget:
get("/your-route-path/", (request, response) -> {
// your callback code
});
3.2. API Hello World
Vamos criar um serviço web simples que tenha duas rotas para solicitações GET e retorne mensagens de "Olá" em resposta. Essas rotas usam o métodoget, que é uma importação estática da classespark.Spark:
import static spark.Spark.*;
public class HelloWorldService {
public static void main(String[] args) {
get("/hello", (req, res)->"Hello, world");
get("/hello/:name", (req,res)->{
return "Hello, "+ req.params(":name");
});
}
}
O primeiro argumento para o métodoget é o caminho para a rota. A primeira rota contém um caminho estático que representa apenas um único URI (“/hello”).
O caminho da segunda rota (“/hello/:name”) contém um espaço reservado para o parâmetro“name”, conforme indicado pelo prefácio do parâmetro com dois pontos (“:”). Essa rota será chamada em resposta a solicitações GET para URIs, como“/hello/Joe”e“/hello/Mary”.
O segundo argumento para o métodoget é umlambda expression dando um sabor de programação funcional a este framework.
A expressão lambda tem solicitação e resposta como argumentos e ajuda a retornar a resposta. Vamos colocar nossa lógica de controlador na expressão lambda para as rotas da API REST, como veremos mais adiante neste tutorial.
3.3. Testando a API Hello World
Depois de executar a classeHelloWorldService como uma classe Java normal, você poderá acessar o serviço em sua porta padrão de4567 usando as rotas definidas com o métodoget acima.
Vejamos a solicitação e a resposta para a primeira rota:
Solicitação:
GET http://localhost:4567/hello
Resposta:
Hello, world
Vamos testar a segunda rota, passando o parâmetroname em seu caminho:
Solicitação:
GET http://localhost:4567/hello/example
Resposta:
Hello, example
Veja como o posicionamento do texto“example” no URI foi usado para corresponder ao padrão de rota“/hello/:name” - fazendo com que a função de manipulador de retorno de chamada da segunda rota fosse invocada.
4. Projetando um serviço RESTful
Nesta seção, projetaremos um serviço da web REST simples para a seguinte entidadeUser:
public class User {
private String id;
private String firstName;
private String lastName;
private String email;
// constructors, getters and setters
}
4.1. Rotas
Vamos listar as rotas que compõem nossa API:
-
GET / users - obtenha uma lista de todos os usuários
-
GET / users /: id - obtém o usuário com o ID fornecido
-
POST / users /: id - adicionar um usuário
-
PUT / users /: id - edita um usuário em particular
-
OPTIONS / users /: id - verifica se existe um usuário com um determinado ID
-
DELETE / users /: id - exclui um usuário específico
4.2. O Serviço do Usuário
Abaixo está a interfaceUserService que declara as operações CRUD para a entidadeUser:
public interface UserService {
public void addUser (User user);
public Collection getUsers ();
public User getUser (String id);
public User editUser (User user)
throws UserException;
public void deleteUser (String id);
public boolean userExist (String id);
}
Para fins de demonstração, fornecemos uma implementaçãoMap dessa interfaceUserService no código GitHub para simular a persistência. You can supply your own implementation with the database and persistence layer of your choice.
4.3. A Estrutura de Resposta JSON
Abaixo está a estrutura JSON das respostas usadas em nosso serviço REST:
{
status:
message:
data:
}
O valor do campostatus pode serSUCCESS ouERROR. O campodata conterá a representação JSON dos dados de retorno, comoUser ou coleção deUsers.
Quando não houver dados sendo retornados, ou sestatus forERROR, preencheremos o campomessage para transmitir o motivo do erro ou da falta de dados de retorno.
Vamos representar a estrutura JSON acima usando uma classe Java:
public class StandardResponse {
private StatusResponse status;
private String message;
private JsonElement data;
public StandardResponse(StatusResponse status) {
// ...
}
public StandardResponse(StatusResponse status, String message) {
// ...
}
public StandardResponse(StatusResponse status, JsonElement data) {
// ...
}
// getters and setters
}
ondeStatusResponse é umenum definido como abaixo:
public enum StatusResponse {
SUCCESS ("Success"),
ERROR ("Error");
private String status;
// constructors, getters
}
5. Implementando serviços RESTful
Agora vamos implementar as rotas e manipuladores para nossa API REST.
5.1. Criação de controladores
A seguinte classe Java contém as rotas para nossa API, incluindo os verbos e caminhos e um resumo dos manipuladores para cada rota:
public class SparkRestExample {
public static void main(String[] args) {
post("/users", (request, response) -> {
//...
});
get("/users", (request, response) -> {
//...
});
get("/users/:id", (request, response) -> {
//...
});
put("/users/:id", (request, response) -> {
//...
});
delete("/users/:id", (request, response) -> {
//...
});
options("/users/:id", (request, response) -> {
//...
});
}
}
Mostraremos a implementação completa de cada manipulador de rota nas subseções a seguir.
5.2. Adicionar usuário
Abaixo está o manipulador de resposta do métodopost que adicionará umUser:
post("/users", (request, response) -> {
response.type("application/json");
User user = new Gson().fromJson(request.body(), User.class);
userService.addUser(user);
return new Gson()
.toJson(new StandardResponse(StatusResponse.SUCCESS));
});
Note: Neste exemplo, a representação JSON do objetoUser é passada como o corpo bruto de uma solicitação POST.
Vamos testar a rota:
Solicitação:
POST http://localhost:4567/users { "id": "1012", "email": "[email protected]", "firstName": "Mac", "lastName": "Mason1" }
Resposta:
{ "status":"SUCCESS" }
5.3. Obter todos os usuários
Abaixo está o manipulador de resposta do métodoget que retorna todos os usuários deUserService:
get("/users", (request, response) -> {
response.type("application/json");
return new Gson().toJson(
new StandardResponse(StatusResponse.SUCCESS,new Gson()
.toJsonTree(userService.getUsers())));
});
Agora vamos testar a rota:
Solicitação:
GET http://localhost:4567/users
Resposta:
{ "status":"SUCCESS", "data":[ { "id":"1014", "firstName":"John", "lastName":"Miller", "email":"[email protected]" }, { "id":"1012", "firstName":"Mac", "lastName":"Mason1", "email":"[email protected]" } ] }
5.4. Obter usuário por id
Abaixo está o manipulador de resposta do métodoget que retorna umUser com oid fornecido:
get("/users/:id", (request, response) -> {
response.type("application/json");
return new Gson().toJson(
new StandardResponse(StatusResponse.SUCCESS,new Gson()
.toJsonTree(userService.getUser(request.params(":id")))));
});
Agora vamos testar a rota:
Solicitação:
GET http://localhost:4567/users/1012
Resposta:
{ "status":"SUCCESS", "data":{ "id":"1012", "firstName":"Mac", "lastName":"Mason1", "email":"[email protected]" } }
5.5. Editar um usuário
Abaixo está o manipulador de resposta do métodoput, que edita o usuário tendoid fornecido no padrão de rota:
put("/users/:id", (request, response) -> {
response.type("application/json");
User toEdit = new Gson().fromJson(request.body(), User.class);
User editedUser = userService.editUser(toEdit);
if (editedUser != null) {
return new Gson().toJson(
new StandardResponse(StatusResponse.SUCCESS,new Gson()
.toJsonTree(editedUser)));
} else {
return new Gson().toJson(
new StandardResponse(StatusResponse.ERROR,new Gson()
.toJson("User not found or error in edit")));
}
});
Note: Neste exemplo, os dados são passados no corpo bruto de uma solicitação POST como um objeto JSON cujos nomes de propriedade correspondem aos campos do objetoUser a ser editado.
Vamos testar a rota:
Solicitação:
PUT http://localhost:4567/users/1012 { "lastName": "Mason" }
Resposta:
{ "status":"SUCCESS", "data":{ "id":"1012", "firstName":"Mac", "lastName":"Mason", "email":"[email protected]" } }
5.6. Excluir um usuário
Abaixo está o manipulador de resposta do métododelete, que excluiráUser com oid fornecido:
delete("/users/:id", (request, response) -> {
response.type("application/json");
userService.deleteUser(request.params(":id"));
return new Gson().toJson(
new StandardResponse(StatusResponse.SUCCESS, "user deleted"));
});
Agora, vamos testar a rota:
Solicitação:
DELETE http://localhost:4567/users/1012
Resposta:
{ "status":"SUCCESS", "message":"user deleted" }
5.7. Verifique se o usuário existe
O métodooptions é uma boa escolha para verificação condicional. Abaixo está o manipulador de resposta do métodooptions, que verificará se existe umUser com oid fornecido:
options("/users/:id", (request, response) -> {
response.type("application/json");
return new Gson().toJson(
new StandardResponse(StatusResponse.SUCCESS,
(userService.userExist(
request.params(":id"))) ? "User exists" : "User does not exists" ));
});
Agora vamos testar a rota:
Solicitação:
OPTIONS http://localhost:4567/users/1012
Resposta:
{ "status":"SUCCESS", "message":"User exists" }
6. Conclusão
Neste artigo, tivemos uma rápida introdução à estrutura Spark para um rápido desenvolvimento da Web.
Essa estrutura é promovida principalmente para gerar microsserviços em Java. Node.js desenvolvedores com conhecimento em Java que desejam aproveitar bibliotecas construídas em bibliotecas JVM devem se sentir em casa usando esta estrutura.
E como sempre, você pode encontrar todas as fontes para este tutorial emGithub project.