Introduzione ad Akka HTTP

1. Panoramica

In questo tutorial, con l'aiuto dei modelli Actor & Stream di Akka, impareremo come configurare Akka per creare un'API HTTP che fornisce operazioni CRUD di base.

2. Dipendenze di Maven

Per iniziare, diamo un'occhiata alle dipendenze richieste per iniziare a lavorare con 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 

Possiamo, ovviamente, trovare l'ultima versione di queste librerie Akka su Maven Central.

3. Creazione di un attore

Ad esempio, creeremo un'API HTTP che ci consentirà di gestire le risorse utente. L'API supporterà due operazioni:

  • creazione di un nuovo utente
  • caricamento di un utente esistente

Prima di poter fornire un'API HTTP, dobbiamo implementare un attore che fornisca le operazioni di cui abbiamo bisogno:

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()); }; } }

Fondamentalmente, stiamo estendendo la classe AbstractActor e implementando il suo metodo createReceive () .

All'interno di createReceive () , stiamo mappando i tipi di messaggi in arrivo su metodi che gestiscono i messaggi del rispettivo tipo.

I tipi di messaggio sono semplici classi contenitore serializzabili con alcuni campi che descrivono una determinata operazione . GetUserMessage e dispone di un unico campo userId per identificare l'utente da caricare. CreateUserMessage contiene un oggetto utente con i dati utente necessari per creare un nuovo utente.

Successivamente, vedremo come tradurre le richieste HTTP in arrivo in questi messaggi.

Infine, deleghiamo tutti i messaggi a un'istanza UserService , che fornisce la logica di business necessaria per la gestione degli oggetti utente persistenti.

Inoltre, nota il metodo props () . Sebbene il metodo props () non sia necessario per estendere AbstractActor , tornerà utile in seguito durante la creazione di ActorSystem .

Per una discussione più approfondita sugli attori, dai un'occhiata alla nostra introduzione ad Akka Actors.

4. Definizione delle route HTTP

Avendo un attore che fa il lavoro effettivo per noi, tutto ciò che ci resta da fare è fornire un'API HTTP che delega le richieste HTTP in arrivo al nostro attore.

Akka utilizza il concetto di route per descrivere un'API HTTP. Per ogni operazione abbiamo bisogno di un percorso.

Per creare un server HTTP, estendiamo la classe framework HttpApp e implementiamo il metodo route :

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()); }); }))); } } 
    

Ora, qui c'è una buona quantità di boilerplate, ma nota che seguiamo lo stesso schema di prima delle operazioni di mappatura, questa volta come rotte. Analizziamolo un po '.

All'interno di getUser () , avvolgiamo semplicemente l'ID utente in arrivo in un messaggio di tipo GetUserMessage e inoltriamo quel messaggio al nostro userActor .

Una volta che l'attore ha elaborato il messaggio, viene chiamato il gestore onSuccess , in cui completiamo la richiesta HTTP inviando una risposta con un determinato stato HTTP e un determinato corpo JSON. Usiamo il marshaller di Jackson per serializzare la risposta data dall'attore in una stringa JSON.

All'interno di postUser () , facciamo le cose in modo leggermente diverso, poiché ci aspettiamo un corpo JSON nella richiesta HTTP. Usiamo il metodo entity () per mappare il corpo JSON in entrata in un oggetto User prima di avvolgerlo in un CreateUserMessage e trasmetterlo al nostro attore. Ancora una volta, usiamo Jackson per mappare tra Java e JSON e viceversa.

Poiché HttpApp si aspetta che forniamo un singolo oggetto Route , combiniamo entrambe le rotte in una singola all'interno del metodo rotte . Qui, usiamo la direttiva path per fornire finalmente il percorso dell'URL in cui la nostra API dovrebbe essere disponibile.

Associamo il percorso fornito da postUser () al percorso / utenti . Se la richiesta in arrivo non è una richiesta POST, Akka andrà automaticamente nel ramo orElse e si aspetterà che il percorso sia / users / e il metodo HTTP sia GET.

Se il metodo HTTP è GET, la richiesta verrà inoltrata alla route getUser () . Se l'utente non esiste, Akka restituirà lo stato HTTP 404 (non trovato). Se il metodo non è né un POST né un GET, Akka restituirà lo stato HTTP 405 (metodo non consentito).

Per ulteriori informazioni su come definire le route HTTP con Akka, dai un'occhiata ai documenti di Akka.

5. Avvio del server

Dopo aver creato un'implementazione HttpApp come sopra, possiamo avviare il nostro server HTTP con un paio di righe di codice:

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); }

Creiamo semplicemente un ActorSystem con un singolo attore di tipo UserActor e avviamo il server su localhost .

6. Conclusione

In questo articolo, abbiamo appreso le basi di Akka HTTP con un esempio che mostra come configurare un server HTTP ed esporre gli endpoint per creare e caricare risorse, in modo simile a un'API REST.

Come al solito, il codice sorgente presentato qui può essere trovato su GitHub.