Introdução ao Akka HTTP
1. Visão geral
2. Dependências do Maven
Para começar, vamos dar uma olhada nas dependências necessárias para começar a trabalhar com Akka HTTP:
com.typesafe.akka
akka-http_2.12
10.0.11
com.typesafe.akka
akka-stream_2.12
2.5.11
com.typesafe.akka
akka-http-jackson_2.12
10.0.11
com.typesafe.akka
akka-http-testkit_2.12
10.0.11
test
Podemos, é claro, encontrar a versão mais recente dessas bibliotecas Akka emMaven Central.
3. Criando um ator
Como exemplo, vamos construir uma API HTTP que nos permite gerenciar os recursos do usuário. A API suportará duas operações:
-
criando um novo usuário
-
carregando um usuário existente
Antes de fornecermos uma API HTTP,we’ll need to implement an actor that provides the operations we need:
class UserActor extends AbstractActor {
private UserService userService = new UserService();
static Props props() {
return Props.create(UserActor.class);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(CreateUserMessage.class, handleCreateUser())
.match(GetUserMessage.class, handleGetUser())
.build();
}
private FI.UnitApply handleCreateUser() {
return createUserMessage -> {
userService.createUser(createUserMessage.getUser());
sender()
.tell(new ActionPerformed(
String.format("User %s created.", createUserMessage.getUser().getName())), getSelf());
};
}
private FI.UnitApply handleGetUser() {
return getUserMessage -> {
sender().tell(userService.getUser(getUserMessage.getUserId()), getSelf());
};
}
}
Basicamente, estamos estendendo a classeAbstractActore implementando seu métodocreateReceive().
EmcreateReceive(), somosmapping incoming message types para métodos que manipulam mensagens do respectivo tipo.
The message types are simple serializable container classes with some fields that describe a certain operation. GetUserMessagee tem um único campouserId para identificar o usuário a ser carregado. CreateUserMessage contém um objetoUser com os dados do usuário de que precisamos para criar um novo usuário.
Posteriormente, veremos como traduzir as solicitações HTTP de entrada nessas mensagens.
Por fim, delegamos todas as mensagens a uma instânciaUserService, que fornece a lógica de negócios necessária para gerenciar objetos de usuário persistentes.
Além disso, observe o métodoprops(). While the props() method isn’t necessary for extending*AbstractActor*, será útil mais tarde, ao criar oActorSystem.
Para uma discussão mais aprofundada sobre os atores, dê uma olhada em nossointroduction to Akka Actors.
4. Definindo rotas HTTP
Ter um ator que faz o trabalho real para nós,all we have left to do is to provide an HTTP API that delegates incoming HTTP requests to our actor.
Akka usa o conceito de rotas para descrever uma API HTTP. For each operation, we need a route.
Para criar um servidor HTTP, estendemos a classe de estruturaHttpAppe implementamos o métodoroutes:
class UserServer extends HttpApp {
private final ActorRef userActor;
Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
UserServer(ActorRef userActor) {
this.userActor = userActor;
}
@Override
public Route routes() {
return path("users", this::postUser)
.orElse(path(segment("users").slash(longSegment()), id -> route(getUser(id))));
}
private Route getUser(Long id) {
return get(() -> {
CompletionStage> user =
PatternsCS.ask(userActor, new GetUserMessage(id), timeout)
.thenApply(obj -> (Optional) obj);
return onSuccess(() -> user, performed -> {
if (performed.isPresent())
return complete(StatusCodes.OK, performed.get(), Jackson.marshaller());
else
return complete(StatusCodes.NOT_FOUND);
});
});
}
private Route postUser() {
return route(post(() -> entity(Jackson.unmarshaller(User.class), user -> {
CompletionStage userCreated =
PatternsCS.ask(userActor, new CreateUserMessage(user), timeout)
.thenApply(obj -> (ActionPerformed) obj);
return onSuccess(() -> userCreated, performed -> {
return complete(StatusCodes.CREATED, performed, Jackson.marshaller());
});
})));
}
}
Agora, há uma boa quantidade de clichê aqui, mas observe que seguimos o mesmo padrão de antes demapping operations, this time as routes. Vamos dividir um pouco.
EmgetUser(), simplesmente envolvemos o ID do usuário de entrada em uma mensagem do tipoGetUserMessagee encaminhamos essa mensagem para o nossouserActor.
Depois que o ator processou a mensagem, o manipuladoronSuccess é chamado, no qualcomplete a solicitação HTTP enviando uma resposta com um determinado status HTTP e um determinado corpo JSON. Usamos o marshallerJackson para serializar a resposta dada pelo ator em uma string JSON.
EmpostUser(), fazemos as coisas de maneira um pouco diferente, pois esperamos um corpo JSON na solicitação HTTP. Usamos o métodoentity() para mapear o corpo JSON de entrada em um objetoUser antes de envolvê-lo em umCreateUserMessagee passá-lo para nosso ator. Novamente, usamos Jackson para mapear entre Java e JSON e vice-versa.
Since HttpApp expects us to provide a single Route object, we combine both routes to a single one within the routes method. Aqui, usamos a diretivapath para finalmente fornecer o caminho de URL no qual nossa API deve estar disponível.
Vinculamos a rota fornecida porpostUser() ao caminho/users. Se a solicitação de entrada não for uma solicitação POST, a Akka irá automaticamente para o branchorElse e espera que o caminho seja/users/<id>. Dependendo do método HTTP de entrada, a solicitação será encaminhada para a rotagetUser() ou, se ainda não corresponder, retornará o status HTTP 404.
Para obter mais informações sobre como definir rotas HTTP com Akka, dê uma olhada emAkka docs.
5. Iniciando o servidor
Depois de criar uma implementaçãoHttpApp como acima, podemos iniciar nosso servidor HTTP com algumas linhas de código:
public static void main(String[] args) throws Exception {
ActorSystem system = ActorSystem.create("userServer");
ActorRef userActor = system.actorOf(UserActor.props(), "userActor");
UserServer server = new UserServer(userActor);
server.startServer("localhost", 8080, system);
}
Simplesmente criamos umActorSystem com um único ator do tipoUserActore iniciamos o servidor emlocalhost.
6. Conclusão
Neste artigo, aprendemos sobre os conceitos básicos do Akka HTTP com um exemplo que mostra como configurar um servidor HTTP e expor pontos de extremidade para criar e carregar recursos, semelhante a uma API REST.
Como de costume, o código-fonte apresentado aqui pode ser encontrado emGitHub.