Skip to content

Commit

Permalink
doc: Include quickstart in docs (#4381)
Browse files Browse the repository at this point in the history
* with zip files to download
* check samples in ci
* akka-http.version
* release issue
* no ./sbt
* remove intellij page
  • Loading branch information
patriknw authored Apr 18, 2024
1 parent c217221 commit 339108e
Show file tree
Hide file tree
Showing 47 changed files with 2,026 additions and 30 deletions.
81 changes: 81 additions & 0 deletions .github/workflows/check-samples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Check samples

on:
push:
branches:
- main

concurrency:
# Only run once for latest commit per ref and cancel other (previous) runs.
group: ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
check-samples:
name: Check Sample Projects
runs-on: ubuntu-22.04
steps:
- name: Checkout
# https://github.com/actions/checkout/releases
# v4.1.1
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
with:
fetch-depth: 0

- name: Checkout GitHub merge
if: github.event.pull_request
run: |-
git fetch origin pull/${{ github.event.pull_request.number }}/merge:scratch
git checkout scratch
- name: Cache Coursier cache
# https://github.com/coursier/cache-action/releases
# v6.4.5
uses: coursier/cache-action@1ff273bff02a8787bc9f1877d347948af647956d

- name: Set up JDK 17
# https://github.com/coursier/setup-action/releases
# v1.3.5
uses: coursier/setup-action@7bde40eee928896f074dbb76d22dd772eed5c65f
with:
jvm: temurin:1.17

- name: Gather version
# some cleanup of the sbt output to get the version sbt will use when publishing below
run: |-
sbt --no-colors "print akka-actor/version" | tail -n 1 | tr -d '\n' > ~/.version
echo [$(cat ~/.version)]
# useful for debugging: hexdump -c ~/.version
- name: Publish artifacts locally
run: |-
sbt "publishLocal; publishM2"
- name: Test akka-http-quickstart-java
run: |-
cd samples/akka-http-quickstart-java
mvn test -nsu -ntp -Dakka-http.version=`cat ~/.version`
- name: Test akka-http-quickstart-scala
run: |-
cd samples/akka-http-quickstart-scala
sbt test -Dakka-http.version=`cat ~/.version`
- name: Email on failure
if: ${{ failure() }}
uses: dawidd6/action-send-mail@6063705cefe50cb915fc53bb06d4049cae2953b2
with:
server_address: smtp.gmail.com
server_port: 465
secure: true
username: ${{secrets.MAIL_USERNAME}}
password: ${{secrets.MAIL_PASSWORD}}
subject: "Failed: ${{ github.workflow }} / ${{ github.job }}"
to: ${{secrets.MAIL_SEND_TO}}
from: Akka CI
body: |
Job ${{ github.job }} in workflow ${{ github.workflow }} of ${{github.repository}} failed!
https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:

- name: Publish
run: |-
scripts/prepare-downloads.sh
eval "$(ssh-agent -s)"
echo $AKKA_RSYNC_GUSTAV | base64 -d > .github/id_rsa
chmod 600 .github/id_rsa
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ target
*.vim
.ensime*
.bsp

# attachments created by scripts/prepare-downloads.sh
docs/src/main/paradox/attachments
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,10 @@ lazy val docs = project("docs")
writer = new Writer(serializerPlugins = Writer.defaultPlugins(paradoxDirectives.value)))
},
paradoxGroups := Map("Language" -> Seq("Scala", "Java")),
paradoxRoots := List(
"index.html",
"quickstart-java/index.html",
"quickstart-scala/index.html"),
Compile / paradoxProperties ++= Map(
"project.name" -> "Akka HTTP",
"canonical.base_url" -> "https://doc.akka.io/docs/akka-http/current",
Expand Down
4 changes: 2 additions & 2 deletions docs/release-train-issue-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Key links:

- [ ] Check that open PRs and issues assigned to the milestone are reasonable
- [ ] Update the Change date in the LICENSE file.
- [ ] Update the Akka HTTP version in the samples to $VERSION$, otherwise the published zip files of the samples will have the old version.
- [ ] For minor or major versions, add a release notes entry in `docs/src/main/paradox/release-notes/`.
- [ ] Create a new milestone for the [next version](https://github.com/akka/akka-http/milestones)
- [ ] Close the [$VERSION$ milestone](https://github.com/akka/akka-http/milestones?direction=asc&sort=due_date)
Expand Down Expand Up @@ -72,8 +73,7 @@ For minor or major releases:
- [ ] Add the released version to `project/MiMa.scala` to the `mimaPreviousArtifacts` key *of all current compatible branches*.
- [ ] Update [akka-dependencies bom](https://github.com/lightbend/akka-dependencies) and version for [Akka module versions](https://doc.akka.io/docs/akka-dependencies/current/) in [akka-dependencies repo](https://github.com/akka/akka-dependencies)
- [ ] Update [Akka Guide samples](https://github.com/akka/akka-platform-guide)
- [ ] Update quickstarts:
- [ ] [Akka HTTP Java](https://github.com/akka/akka-http-quickstart-java.g8/blob/main/src/main/g8/default.properties)
- [ ] Update sbt new (g8) template:
- [ ] [Akka HTTP Scala](https://github.com/akka/akka-http-quickstart-scala.g8/blob/main/src/main/g8/default.properties)
- Close this issue
2 changes: 1 addition & 1 deletion docs/src/main/paradox/contributing.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 9. Contributing
# 10. Contributing

## Welcome!

Expand Down
27 changes: 4 additions & 23 deletions docs/src/main/paradox/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,29 +53,10 @@ Additionally, add the dependencies as below.
group3="com.typesafe.akka" artifact3="akka-http_$scala.binary.version$" version3="AkkaHttpVersion"
}

You may download a packaged version of this project by clicking "Create a project for me!" on the
@scala[[Lightbend Getting Started page](https://developer.lightbend.com/start/?group=akka&project=akka-http-quickstart-scala)]
@java[[Lightbend Getting Started page](https://developer.lightbend.com/start/?group=akka&project=akka-http-quickstart-java)].
You may download a packaged version of this project from:

Alternatively, you can bootstrap a new project with Akka HTTP already configured using the [Giter8](http://www.foundweekends.org/giter8/) template directly via sbt:

@@@ div { .group-scala }
For Scala (sbt)
: ```sh
sbt new akka/akka-http-quickstart-scala.g8
```
@@@
@@@ div { .group-java }
For Java (Maven or Gradle)
: ```sh
sbt new akka/akka-http-quickstart-java.g8
```
From there on the prepared project can be built using Gradle or Maven.
@@@

More instructions can be found on the @scala[[template
project](https://github.com/akka/akka-http-quickstart-scala.g8)]@java[[template
project](https://github.com/akka/akka-http-quickstart-java.g8)].
* @ref[Akka HTTP Quickstart for Scala](quickstart-scala/index.md)
* @ref[Akka HTTP Quickstart for Java](quickstart-java/index.md)

## Routing DSL for HTTP servers

Expand Down Expand Up @@ -255,4 +236,4 @@ akka-http-jackson
@@@

akka-http-jwt
: Provides directives for validating and extracting JSON Web Tokens (JWT) from requests. Details can be found in the section @ref[JWT Directives](routing-dsl/directives/jwt-directives/jwt.md)
: Provides directives for validating and extracting JSON Web Tokens (JWT) from requests. Details can be found in the section @ref[JWT Directives](routing-dsl/directives/jwt-directives/jwt.md)
2 changes: 1 addition & 1 deletion docs/src/main/paradox/native-image.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Building Native Images
# 9. Building Native Images


Building native images with Akka HTTP is supported.
Expand Down
15 changes: 15 additions & 0 deletions docs/src/main/paradox/quickstart-java/backend-actor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Backend Actor logic
-------------------

In this example, the backend only uses one basic actor. In a real system, we would have many actors interacting with each other and perhaps, multiple data stores and microservices.

An interesting side-note to add here is about when using actors in applications like this adds value over just providing functions that would return `CompletionStage`s.
In fact, if your logic is stateless and very simple request/reply style, you may not need to back it with an Actor. Actors do shine when you need to keep some form of state and allow various requests to access something in (or *through*) an Actor. The other stellar feature of actors, that futures would not handle, is scaling-out onto a cluster very easily, by using @extref[Cluster Sharding](akka-docs:typed/cluster-sharding.html) or other @extref[location-transparent](akka-docs:general/remoting.html) techniques.

However, the focus of this tutorial is on how to interact with an Actor backend from within Akka HTTP -- not on the actor itself, so we'll keep it very simple.

The sample code in the `UserRegistry` is very simple. It keeps registered users in a `Set`. Once it receives messages it matches them to the defined cases to determine which action to take:

@@snip [UserRegistry.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRegistry.java) { #user-registry-actor }

If you feel you need to brush up on your Akka Actor knowledge, the @extref[Getting Started Guide](akka-docs:guide/index.html)reviews actor concepts in the context of a simple Internet of Things (IoT) example.
127 changes: 127 additions & 0 deletions docs/src/main/paradox/quickstart-java/http-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
HTTP Server Logic
-----------------

The main class, `QuickstartServer`, is runnable because it has a `main` method, as shown in the following snippet.
This class is intended to "bring it all together", it is the main class that will start the `ActorSystem` with the root
behavior which bootstraps all actors and other dependencies (database connections etc).

@@snip [QuickstartServer.java](/samples/akka-http-quickstart-java/src/main/java/com/example/QuickstartApp.java) { #main-class }

Notice that we've separated out the `UserRoutes` class, in which we'll put all our actual route definitions.
This is a good pattern to follow, especially once your application starts to grow and you'll need some form of
compartmentalizing them into groups of routes handling specific parts of the exposed API.

## Binding endpoints

Each Akka HTTP `Route` contains one or more `akka.http.javadsl.server.Directive`, such as: `path`, `get`, `post`, `complete`, etc. There is also a @ref[low-level API](../server-side/low-level-api.md) that allows to inspect requests and create responses manually. For the user registry service, the example needs to support the actions listed below. For each, we can identify a path, the HTTP method, and return value:

| Functionality | HTTP Method | Path | Returns |
|--------------------|-------------|------------|----------------------|
| Create a user | POST | /users | Confirmation message |
| Retrieve a user | GET | /users/$ID | JSON payload |
| Remove a user | DELETE | /users/$ID | Confirmation message |
| Retrieve all users | GET | /users | JSON payload |

In our app the definition of the `Route` is separated out into the class `UserRoutes` and available through the factory method `userRoutes`.

In larger applications we'd define separate subsystems in different places and then combine combine the various routes of our
application into a big using the concat directive like this: `Route route = concat(UserRoutes.userRoutes(), healthCheckRoutes, ...)`

Let's look at the pieces of the example `Route` that bind the endpoints, HTTP methods, and message or payload for each action.

### Retrieving and creating users

The definition of the endpoint to retrieve and create users look like the following:

@@snip [UserRoutes.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRoutes.java) { #users-get-post }

A Route is constructed by nesting various *directives* which route an incoming request to the apropriate handler block.
Note the following building blocks from the snippet:

**Generic functionality**

The following directives are used in the above example:

* `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.
* `concat`: 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 contains information about why the request was rejected.

**Retrieving users**

* `get` : matches against `GET` HTTP method.
* `complete` : completes a request which means creating and returning a response from the arguments.

**Creating a user**

* `post` : matches against `POST` HTTP method.
* `entity(Unmarshaller<T>, T -> Directive))` : 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 which can be different combinations of a HTTP status code, an object to "marshall" into a response body using a marshaller parameter.

### Retrieving and removing a user

Next, the example defines how to retrieve and remove a user. In this case, the URI must include the user's id in the form: `/users/$ID`. See if you can identify the code that handles that in the following snippet. This part of the route includes logic for both the GET and the DELETE methods.

@@snip [QuickstartServer.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRoutes.java) { #users-get-delete }

This part of the `Route` contains the following:

**Generic functionality**

The following directives are used in the above example:

* `pathPrefix("users")` : the path that is used to match the incoming request against.
* `concat`: 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.
* `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 @ref[pattern matchers](../routing-dsl/path-matchers.md#basic-pathmatchers) for more information.

**Retrieving a user**

* `get` : matches against `GET` HTTP method.
* `complete` : completes a request which means creating and returning a response from the arguments.

Let's break down the logic handling the incoming request:

@@snip [UserRoutes.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRoutes.java) { #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.

**Deleting a user**

* `delete` : matches against the Http directive `DELETE`.

The logic for handling delete requests is as follows:

@@snip [UserRoutes.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRoutes.java) { #users-delete-logic }

So we 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

Below is the complete `Route` definition from the sample application:

@@snip [UserRoutes.java](/samples/akka-http-quickstart-java/src/main/java/com/example/UserRoutes.java) { #all-routes }

Note that one might want to separate those routes into smaller route values and `concat` them together into the `userRoutes`
value - allowing for separation of concerns and get smaller routing trees.

## Binding the HTTP server

Binding the `Route` to a HTTP server on a TCP port is done from the root behavior actor on startup through the separate method
`startHttpServer`, we have introduced it to avoid accidentally accessing internal state of the bootstrap actor.

The `bindAndhandle` method that does the actual binding takes three parameters; `routes`, the hostname, and the port.
Note that binding happens asynchronously and therefore the `bindAndHandle` method returns a `Future` which completes with
an object representing the binding or fails if binding the HTTP route failed, for example if the port is already taken.

To make sure our application stops if it cannot bind we terminate the actor system if there is a failure.

@@snip [QuickstartApp.java](/samples/akka-http-quickstart-java/src/main/java/com/example/QuickstartApp.java) { #start-http-server }

In `QuickstartApp.java`, you will also find the code that ties everything together by starting the various actors in a
root behavior. By watching the user registry actor and not handling the `Terminated` message we make sure that if
it stops or craches the root behavior crashes and stops the `ActorSystem` itself.

@@snip [QuickstartApp.java](/samples/akka-http-quickstart-java/src/main/java/com/example/QuickstartApp.java) { #main-class }



Let's move on to the actor that handles registration.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 339108e

Please sign in to comment.