This is an automated email from the ASF dual-hosted git repository. fanningpj pushed a commit to branch wip-adding-tests in repository https://gitbox.apache.org/repos/asf/incubator-pekko-http-quickstart-scala-g8.git
commit 1d766ec6d689477aae1f00582b4f6e6225df1ac0 Author: henrikengstrom <[email protected]> AuthorDate: Wed Jul 19 15:48:37 2017 -0400 Further fixes based on feedback. --- docs/src/main/paradox/backend.md | 6 +- docs/src/main/paradox/index.md | 30 ++++++-- docs/src/main/paradox/intellij-idea.md | 4 +- docs/src/main/paradox/running-the-application.md | 22 ++++-- docs/src/main/paradox/server-class.md | 89 +++++++++++----------- src/main/g8/build.sbt | 8 +- .../lightbend/akka/http/sample/JsonSupport.scala | 3 +- .../akka/http/sample/QuickstartServer.scala | 73 ++++++++++-------- .../akka/http/sample/UserRegistryActor.scala | 16 ++-- 9 files changed, 149 insertions(+), 102 deletions(-) diff --git a/docs/src/main/paradox/backend.md b/docs/src/main/paradox/backend.md index 8de403b..788a694 100644 --- a/docs/src/main/paradox/backend.md +++ b/docs/src/main/paradox/backend.md @@ -1,9 +1,11 @@ Backend ------- -It is probably an exaggeration to call this for a "backend" since it, in this sample, only consists of a basic actor. In a real system, we would have many actors interacting with each other and perhaps a database or microservices. However, since the focus of this tutorial is on Akka HTTP, it is not very important what this backend does but more how to interact with a backend from within Akka HTTP. Hopefully, you already have a good grasp of how to communicate back and forth between Akka [...] +It is probably an exaggeration to call this for a "backend" since it, in this sample, only consists of a basic actor. In a real system, we would have many actors interacting with each other and perhaps a database or microservices. Also, we suggest designing the backend first and thereafter expose its functionality via an HTTP server. -If you feel you should brush up your Akka Actor knowledge, then the [getting started guide]((http://developer.lightbend.com/guides/akka-quickstart-scala/)) for Akka actors tutorial is a good start. +However, since the focus of this tutorial is on Akka HTTP, it is not very important what this backend does but more how to interact with a backend from within Akka HTTP. Hopefully, you already have a good grasp of how to communicate back and forth between Akka HTTP and actors by now. + +If you feel you should brush up your Akka Actor knowledge, then the [Quickstart guide]((http://developer.lightbend.com/guides/akka-quickstart-scala/)) for Akka actors tutorial is a good start. The sample code in the `UserRegistryActor` is very simple. It keeps registered users in a `Set`. Once it receives messages it will match those into what action it should take: diff --git a/docs/src/main/paradox/index.md b/docs/src/main/paradox/index.md index 72b554a..a1408a5 100644 --- a/docs/src/main/paradox/index.md +++ b/docs/src/main/paradox/index.md @@ -9,7 +9,7 @@ This Hello World example demonstrates some Akka HTTP fundamentals. Within 30 min ## Prerequisite -Having a basic understanding of Akka actors will help the reader of this guide. There is a [getting started guide](https://developer.lightbend.com/guides/akka-quickstart-scala/) for Akka actors should you feel like brushing up your knowledge thereof. +Having a basic understanding of Akka actors will help the reader of this guide. There is a [Quickstart guide](https://developer.lightbend.com/guides/akka-quickstart-scala/) for Akka actors should you feel like brushing up your knowledge thereof. ## Downloading the example @@ -35,7 +35,7 @@ To run Hello World: In a console, change directories to the top level of the unzipped project. -For example, if you used the default project name, `akka-http-quickstart-scala`, and extracted the project to your root directory, from the root directory, enter: cd akka-http-quickstart-scala +For example, if you used the default project name, `akka-http-quickstart-scala`, and extracted the project to your root directory, from the root directory, enter: Enter `./sbt` on OSX/Linux or run `sbt.bat` on Windows to start [sbt](http://www.scala-sbt.org). @@ -43,6 +43,20 @@ sbt downloads project dependencies. The `>` prompt indicates sbt has started in At the sbt prompt, enter `run`. +OSX/Linux +: ``` +$ cd akka-http-quickstart-scala +$ ./sbt +> run +``` + +Windows +: ``` +$ cd akka-http-quickstart-scala +$ sbt.bat +> run +``` + sbt builds the project and runs Hello World. The output should look something like this: @@ -61,24 +75,24 @@ Congratulations, you just ran your first Akka HTTP app. Now take a look at what ## What Hello World does -When Hello World starts up it creates an ActorSystem and registers so-called routes that are available in this ActorSystem. The routes are bound to a port, in this case, `localhost:8080`. Below is an overview of what things look like when the application starts: +In this example, a simple user registry service represented by an actor is presented to consumers as a [REST service](https://en.wikipedia.org/wiki/Representational_state_transfer). - +When Hello World starts up, it creates an ActorSystem and an instance of the `UserRegistryActor` that implements the functionality of the service. Then the HTTP service is started by binding so-called "routes" to the given interface and port, in this case, `localhost:8080`. Below is an overview of what things look like after the application has started: -In this sample application, we are implementing a [REST service](https://en.wikipedia.org/wiki/Representational_state_transfer). The idea is to build a simple user registry service. + -The endpoints available are `/users` and `/user` with some attached HTTP directives and parameters/payloads. When invoked, endpoints will interact with an actor, "userRegistryActor", which represents the business logic of this sample application. +The value `routes` defines the endpoint `/users` that route requests to the business logic actor. Routes are defined in terms of so-called "directives" which are basic building blocks of Akka Http that define how requests and responses are handled and how data is translated from HTTP to and from your domain objects. In the following sections, we will take a detailed look at the individual pieces of this application. ## The Akka HTTP philosophy -Akka HTTP is designed specifically as "not-a-framework," not because we don’t like frameworks, but for use cases where a framework is not the right choice. Akka HTTP is for building integration layers based on HTTP and as such tries to "stay on the sidelines." Therefore, a typical application doesn't sit on top of Akka HTTP but instead on top of whatever makes sense and uses Akka HTTP merely for the HTTP integration needs. +Akka HTTP is designed specifically as "not-a-framework," not because we don’t like frameworks, but for use cases where a framework is not the right choice. Akka HTTP is for building integration layers based on HTTP and as such tries to "stay on the sidelines." Therefore, a typical application doesn't sit on top of Akka HTTP but instead on top of whatever makes sense and uses Akka HTTP merely for the HTTP integration needs. Should you look for a web framework it is recommended to take a [...] @@@index -* [The server](server-class.md) * [The backend](backend.md) +* [The server](server-class.md) * [JSON](json.md) * [Running the application](running-the-application.md) * [IntelliJ IDEA](intellij-idea.md) diff --git a/docs/src/main/paradox/intellij-idea.md b/docs/src/main/paradox/intellij-idea.md index f55a8ad..9dd3629 100644 --- a/docs/src/main/paradox/intellij-idea.md +++ b/docs/src/main/paradox/intellij-idea.md @@ -9,11 +9,11 @@ Open IntelliJ and select File -> Open... and point to the directory where you ha  -Fill out the settings according to the above and press `OK` to import the project. If IntelliJ will warn about missing Scala SDK, it is only to follow the instructions to add support. +Fill out the settings according to the above and press `OK` to import the project. If IntelliJ will warn about missing Scala SDK, just follow the instructions to add support. ## Inspecting the code -If we open up the file `src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala` we can see a lot of lines beginning with //# .... These lines are used as directives for this documentation. To get rid of these lines from the source code we can utilize the awesome Find/Replace functionality in IntelliJ. Select Edit -> Find -> Replace in Path.... Check the Regex box and add the following regex [//#].* and click on Replace in Find Window.... Select to replace all occurrences an [...] +If we open up the file `src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala` we can see a lot of lines beginning with //# .... These lines are used as directives for this documentation. To get rid of these lines from the source code we can utilize the awesome Find/Replace functionality in IntelliJ. Select Edit -> Find -> Replace in Path.... Check the Regex box and add the following regex `[//#].*` and click on Replace in Find Window.... Select to replace all occurrences [...] ## Running the application diff --git a/docs/src/main/paradox/running-the-application.md b/docs/src/main/paradox/running-the-application.md index ac9d6d1..3142b52 100644 --- a/docs/src/main/paradox/running-the-application.md +++ b/docs/src/main/paradox/running-the-application.md @@ -11,13 +11,21 @@ sbt uses a build.sbt file to handle the project. This project’s build.sbt file ## Running the project -We run the application from a console/terminal window: +We run the application from a console/terminal window and enter the following commands: -1. Enter `./sbt` on OSX/Linux or `sbt.bat` on Windows - -sbt downloads project dependencies. The `>` prompt indicates sbt has started in interactive mode. +OSX/Linux +: ``` +$ cd akka-http-quickstart-scala +$ ./sbt +> run +``` -2. At the sbt prompt, enter `run`. +Windows +: ``` +$ cd akka-http-quickstart-scala +$ sbt.bat +> run +``` The output should look like this: @@ -28,9 +36,9 @@ Server online at http://localhost:8080/ Press RETURN to stop... ``` -## Testing the application +## Interacting with the application -The Akka HTTP server is now running, and we will use the [cURL](https://en.wikipedia.org/wiki/CURL) command to test the application. If you prefer to use your browser to test the service then a tool like [RESTClient](http://restclient.net/) may be good to install. +The Akka HTTP server is now running, and we will use the [cURL](https://en.wikipedia.org/wiki/CURL) command to test the application. If you prefer to use your browser to test the service then a tool like [RESTClient](http://restclient.net/) may be good to install. Open another console/terminal window to investigate the functionality of the application. diff --git a/docs/src/main/paradox/server-class.md b/docs/src/main/paradox/server-class.md index c10f9ad..daefde2 100644 --- a/docs/src/main/paradox/server-class.md +++ b/docs/src/main/paradox/server-class.md @@ -1,79 +1,80 @@ -The main class ----------------- +The Akka HTTP server class +-------------------------- -Let's dissect the main class, `QuickstartServer`. We make this class runnable by extending `App` (we will discuss the trait `JsonSupport` later): +Let's dissect the server class, `QuickstartServer`. We make this class runnable by extending `App` (we will discuss the trait `JsonSupport` later): @@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #main-class } -Now that we have a class to run we should add some Akka HTTP fundamentals with which we will build our RESTful web service: - -* define routes bound to endpoints and HTTP directives -* create a server bound to an IP and port that will handle all requests -* add error handling for when something goes wrong - -Let us take a look at each of these steps here below. +Now that we have a class to run we should add some Akka HTTP fundamentals with which we will build our RESTful web service. The aim is to use routes to define endpoints by choosing what to do for which requests. ## Routes For our service we want to define the following endpoints: -| Path | Http directive | Intent | Returns | +| Path | Http method | Intent | Returns | |-------------|-----------------|--------------------|----------------------| -| /user | POST | Create a new user | Confirmation message | -| /user/$ID | GET | Retrieve a user | JSON payload | -| /user/$ID | DELETE | Remove a user | Confirmation message | +| /users | POST | Create a new user | Confirmation message | | /users | GET | Retrieve all users | JSON payload | +| /users/$ID | GET | Retrieve a user | JSON payload | +| /users/$ID | DELETE | Remove a user | Confirmation message | -Akka HTTP provides a [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) to simplify the routes/endpoints definition. Each route is composed of one or more `akka.http.scaladsl.server.Directives`, e.g. `path`, `get`, `post`, `complete`, etc. +Akka HTTP provides a [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) (DSL) to simplify the routes/endpoints definition. Each route is composed of one or more `akka.http.scaladsl.server.Directives`, e.g. `path`, `get`, `post`, `complete`, etc. There is also a [low-level API](http://doc.akka.io/docs/akka-http/current/scala/http/low-level-server-side-api.html) that allows to inspect requests and create responses manually. -### Creating a new user +### Creating and retrieving a user -Let us take a look at the source code for the first endpoint, the `/user` URI with a `POST` directive, used to create a new user: +Let us take a look at the source code for the first two endpoints, the `/users` path with `GET` and `POST` HTTP methods: -@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #user-post } +@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #users-get-post } -The snippet above contains a couple of interesting building blocks: +The snippet above contains a couple of interesting Akka HTTP building blocks: -* `path` : matches against the incoming URI, in this case, we want to get all requests that matches `user`. -* `post` : matches against the incoming Http directive, in this case, we are matching against `POST`. -* `entity(as[User])` : automatically converts the incoming payload, in this case, we expect JSON, into an entity. We will look more at this functionality in the @ref:[JSON](json.md) section. -* `complete` : used to reply back to the request. The `StatusCodes.Created` is translated to Http response code 201. We also send back information to the caller in the form of a string. +**Generic functionality** -When this `Route` is called, we want to create a new user, and we do so by sending a message to the actor `userRegistryActor`. We will look at the implementation of this actor later. +* `pathPrefix("users")` : the path that is used to match the incoming request against. +* `pathEnd` : used on an inner-level to discriminate “path already fully matched” from other alternatives. Will, in this case, match on the "users" path. +* `~`: concatenates two or more route alternatives. Routes are attempted one after another. If a route rejects a request, the next route in the chain is attempted. This continues until a route in the chain produces a response. If all route alternatives reject the request, the concatenated route rejects the route as well. In that case, route alternatives on the next higher level are attempted. If the root level route rejects the request as well, then an error response is returned that con [...] -### Retrieving and removing a user +**Retrieving users** -Next we need to define how to retrieve and remove a user, i.e. for the case when the URI `/user/$ID` is used where `$ID` is the id of the user: +* `get` : matches against `GET` HTTP method. +* `complete` : completes a request which means creating and returning a response from the arguments. -@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #user-get-delete } +**Creating a user** -This Route snippet contains a couple of interesting concepts: +* `post` : matches against `POST` HTTP method. +* `entity(as[User])` : converts the HTTP request body into a domain object of type User. Implicitly, we assume that the request contains application/json content. We will look at how this works in the @ref:[JSON](json.md) section. +* `complete` : completes a request which means creating and returning a response from the arguments. Note, how the tuple (StatusCodes.Created, "...") of type (StatusCode, String) is implicitly converted to a response with the given status code and a text/plain body with the given string. -* `path("user" / Segment) { => user` : this bit of code matches against URIs of the exact format `/user/$ID` and the `Segment` is automatically extracted into the `user` variable so that we can get to the value passed in the URI. For example `/user/Bruce` will populate the `user` variable with the value "Bruce." -* `get` : matches against the incoming Http directive. +### Retrieving and removing a user -Let's break down the "business logic" in the first Route: +Next we need to define how to retrieve and remove a user, i.e. for the case when the URI `/users/$ID` is used where `$ID` is the id of the user: -@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #retrieve-user-info } +@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #users-get-delete } -The code above uses the so-called [ask](http://doc.akka.io/docs/akka/current/scala/actors.html#send-messages) in Akka. This will send a message asynchronously and return a `Future` representing a _possible_ reply. The code above maps the reply to the type `UserInfo`. When the future completes, it will use the second part of the code to evaluate to either `Success`, with or without a result, or a `Failure`. Regardless of the outcome of the future, we should return something to the request [...] +This Route snippet contains a couple of interesting concepts: -The remaining directives used for this route are: +**Generic functionality** -* `~` : fuses `Route`s together - this will become more apparent when you see the complete `Route` definition here below. -* `delete` : matches against the Http directive `DELETE`. +* `pathPrefix("users")` : the path that is used to match the incoming request against. +* `path(Segment) { => user` : this bit of code matches against URIs of the exact format `/users/$ID` and the `Segment` is automatically extracted into the `user` variable so that we can get to the value passed in the URI. For example `/users/Bruce` will populate the `user` variable with the value "Bruce." There is plenty of more features available for handling of URIs, see [pattern matchers](http://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/path-matchers.html#basic-pathmat [...] +* `~`: concatenates two or more route alternatives. Routes are attempted one after another. If a route rejects a request, the next route in the chain is attempted. This continues until a route in the chain produces a response. If all route alternatives reject the request, the concatenated route rejects the route as well. In that case, route alternatives on the next higher level are attempted. If the root level route rejects the request as well, then an error response is returned that con [...] + +**Retrieving a user** -The "business logic" for when deleting a user is straight forward; send an instruction about removing a user to the user registry actor and return a status code to the client (which in this case is `StatusCodes.OK`, i.e. Http status code 200) +* `get` : matches against `GET` HTTP method. +* `complete` : completes a request which means creating and returning a response from the arguments. -### Retrieving all users +Let's break down the "business logic": -Finally we should implement functionality to retrieve all registered users: +@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #retrieve-user-info } + +The `rejectEmptyResponse` here above is a convenience method that automatically unwraps a future, handles an `Option` by converting `Some` into a successful response, returns a HTTP status code 404 for `None`, and passes on to the `ExceptionHandler` in case of an error, which returns the HTTP status code 500 by default. -@@snip [QuickstartServer.scala]($g8src$/scala/com/lightbend/akka/http/sample/QuickstartServer.scala) { #users-get } +**Deleting a user** -This code is based on the same structure as the code above, send a message to the user registry actor using an `ask` and pass on the `Future` to the `complete` method. +* `delete` : matches against the Http directive `DELETE`. -Why do we not use an `onComplete` as we did for when retrieving a particular user? The difference is that when we use `GetUsers` there will always be something returned; an empty list means that there are no registered users. However, when we asked for a particular user ID, there might be the case that there is no such user recorded and we need a way to tell this to the client. When sent a `GetUser(name)` the user registry actor therefore send back an `Option[UserInfo]`. It could be that [...] +The "business logic" for when deleting a user is straight forward; send an instruction about removing a user to the user registry actor, wait for the response and return an appropriate HTTP status code to the client. ### The complete Route @@ -92,7 +93,7 @@ To set up an Akka HTTP server we must first define some implicit values that wil What does the above mean and why do we need it? * `ActorSystem` : the context in which actors will run. What actors, you may wonder? Akka Streams uses actors under the hood, and the actor system defined in this `val` will be picked up and used by Streams. -* `ActorMaterializer` : also Akka Streams related - it uses the materializer to allocate all the necessary resources it needs to run. +* `ActorMaterializer` : while the ActorSystem is the host of all thread pools and live actors, an ActorMaterializer is specific to Akka Streams and is what makes them run. The ActorMaterializer interprets stream descriptions into executable entities which are run on actors, and this is why it requires an ActorSystem to function. With that defined we can move on to instantiate the server: @@ -104,7 +105,7 @@ We should also add code for stopping the server. To do so we use the `StdIn.read ## Error handling -Finally, we should take a look at how to handle errors. We know, as the astute engineers we are, that errors will happen. We should prepare our program for this and error handling should not be an afterthought when we build systems. +Finally, we should take a look at how to [handle errors](http://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/exception-handling.html). We know, as the astute engineers we are, that errors will happen. We should prepare our program for this and error handling should not be an afterthought when we build systems. In this sample, we use a very simple exception handler which catches all unexpected exceptions and responds back to the client with an `InternalServerError` (HTTP status code 500) with an error message and for what URI the exception happened. We extract the URI by using the `extractUri` directive. diff --git a/src/main/g8/build.sbt b/src/main/g8/build.sbt index 030ddd5..c286488 100644 --- a/src/main/g8/build.sbt +++ b/src/main/g8/build.sbt @@ -7,10 +7,14 @@ lazy val root = (project in file(".")). organization := "com.example", scalaVersion := "2.12.2" )), - name := "$name$", + name := "Akka Http Quickstart", libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion, - "com.typesafe.akka" %% "akka-stream" % akkaVersion + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion, + "com.typesafe.akka" %% "akka-stream" % akkaVersion, + "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, + "org.scalatest" %% "scalatest" % "3.0.1" % Test ) ) diff --git a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala index 6f19b73..ffebd52 100644 --- a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala +++ b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/JsonSupport.scala @@ -3,7 +3,8 @@ package com.lightbend.akka.http.sample import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import spray.json.DefaultJsonProtocol -trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { +trait JsonSupport extends SprayJsonSupport { + import DefaultJsonProtocol._ implicit val userJsonFormat = jsonFormat3(User) implicit val usersJsonFormat = jsonFormat1(Users) } diff --git a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala index 1279967..3181755 100644 --- a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala +++ b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/QuickstartServer.scala @@ -35,6 +35,8 @@ object QuickstartServer extends App with JsonSupport { implicit val executionContext: ExecutionContext = system.dispatcher val userRegistryActor: ActorRef = system.actorOf(UserRegistryActor.props, "userRegistryActor") + + // Required by the `ask` (?) method below implicit val timeout = Timeout(5 seconds) //#exception-handler @@ -48,41 +50,50 @@ object QuickstartServer extends App with JsonSupport { //#all-routes lazy val routes: Route = - //#user-post - path("user") { - post { - entity(as[User]) { user => - userRegistryActor ! CreateUser(user) - complete((StatusCodes.Created, s"User ${user.name} created.")) - } - } - } ~ //#user-post - //#user-get-delete - path("user" / Segment) { name => - get { - //#retrieve-user-info - val userInfo: Future[UserInfo] = (userRegistryActor ? GetUser(name)).mapTo[UserInfo] - onComplete(userInfo) { r => - r match { - case Success(UserInfo(Some(user))) => complete(user) - case Success(UserInfo(None)) => complete((StatusCodes.OK, s"User $name is not registered.")) - case Failure(ex) => complete((StatusCodes.InternalServerError, ex)) - } - } - //#retrieve-user-info - } ~ - delete { - userRegistryActor ! DeleteUser(name) - complete((StatusCodes.OK, s"User $name deleted.")) - } - } ~ //#user-get-delete - //#users-get - path("users") { + //#users-get-post + //#users-get-delete + pathPrefix("users") { + //#users-get-delete + pathEnd { get { val users: Future[Users] = (userRegistryActor ? GetUsers).mapTo[Users] complete(users) + } ~ + post { + entity(as[User]) { user => + val userCreated: Future[ActionPerformed] = (userRegistryActor ? CreateUser(user)).mapTo[ActionPerformed] + onComplete(userCreated) { r => + r match { + case Success(ActionPerformed(description)) => complete((StatusCodes.Created, description)) + case Failure(ex) => complete((StatusCodes.InternalServerError, ex)) + } + } + } + } + } ~ + //#users-get-post + //#users-get-delete + path(Segment) { name => + get { + //#retrieve-user-info + val maybeUser: Future[Option[User]] = (userRegistryActor ? GetUser(name)).mapTo[Option[User]] + rejectEmptyResponse { + complete(maybeUser) + } + //#retrieve-user-info + } ~ + delete { + val userDeleted: Future[ActionPerformed] = (userRegistryActor ? DeleteUser(name)).mapTo[ActionPerformed] + onComplete(userDeleted) { r => + r match { + case Success(ActionPerformed(description)) => complete((StatusCodes.OK, description)) + case Failure(ex) => complete((StatusCodes.InternalServerError, ex)) + } + } + } } - } //#users-get + //#users-get-delete + } //#all-routes //#http-server diff --git a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala index 718f63e..7a910ac 100644 --- a/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala +++ b/src/main/g8/src/main/scala/com/lightbend/akka/http/sample/UserRegistryActor.scala @@ -3,13 +3,13 @@ package com.lightbend.akka.http.sample import akka.actor.{ Actor, ActorLogging, Props } import scala.collection.mutable.Set -case class UserInfo(maybeUser: Option[User]) //#user-case-classes case class User(name: String, age: Int, countryOfResidence: String) case class Users(users: Seq[User]) //#user-case-classes object UserRegistryActor { + final case class ActionPerformed(description: String) final case object GetUsers final case class CreateUser(user: User) final case class GetUser(name: String) @@ -24,9 +24,15 @@ class UserRegistryActor extends Actor with ActorLogging { val users: Set[User] = Set.empty[User] def receive = { - case GetUsers => sender ! Users(users.toSeq) - case CreateUser(user) => users += user - case GetUser(name) => sender ! UserInfo(users.find(_.name == name)) - case DeleteUser(name) => users.find(_.name == name) map { user => users -= user } + case GetUsers => + sender ! Users(users.toSeq) + case CreateUser(user) => + users += user + sender ! ActionPerformed(s"User ${user.name} created.") + case GetUser(name) => + sender ! users.find(_.name == name) + case DeleteUser(name) => + users.find(_.name == name) map { user => users -= user } + sender ! ActionPerformed(s"User ${name} deleted.") } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
