Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,26 @@ jobs:
rm -rf src/main/scala src/test/scala src/it/scala
sbt -Dakkaserverless-sdk.version=$SDK_VERSION test:compile

sample-scala-eventsourced-customer-registry:
machine: true
steps:
- checkout-and-merge-to-main
- setup_sbt
- restore_deps_cache
# note: depends on publish local as separate job before this
- copy-from-workspace
- set-sdk-version
- run:
name: Scala Event Sourced Customer Registry
command: |
cd samples/scala-eventsourced-customer-registry
echo "Running sbt with SDK version: '$SDK_VERSION'"
sbt -Dakkaserverless-sdk.version=$SDK_VERSION test
# FIXME integration tests
echo "==== Verifying that generated unmanaged sources compile ===="
rm -rf src/main/scala src/test/scala src/it/scala
sbt -Dakkaserverless-sdk.version=$SDK_VERSION test:compile

sample-scala-valueentity-counter:
machine: true
steps:
Expand Down Expand Up @@ -708,6 +728,10 @@ workflows:
requires:
- checks
- publish-local
- sample-scala-eventsourced-customer-registry:
requires:
- checks
- publish-local
- sample-scala-valueentity-counter:
requires:
- checks
Expand Down
18 changes: 11 additions & 7 deletions docs/src/modules/java/pages/views.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Scala::
[source,proto,indent=0]
.src/main/proto/customer/domain/customer_domain.proto
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/proto/customer/domain/customer_domain.proto[tags=declarations;domain;events]
----

It also assumes a `customer_api.proto` that defines the state stored in the view and returned by queries:
Expand All @@ -166,7 +166,7 @@ Scala::
[source,proto,indent=0]
.src/main/proto/customer/api/customer_api.proto
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/proto/customer/api/customer_api.proto[tags=declarations;view]
----


Expand Down Expand Up @@ -194,7 +194,7 @@ Scala::
[source,proto,indent=0]
.src/main/proto/customer/customer_view.proto
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/proto/customer/view/customer_view.proto[tags=declarations;service-event-sourced]
----

See <<#query>> for more examples of valid query syntax.
Expand Down Expand Up @@ -227,8 +227,11 @@ Scala::
[source,scala,indent=0]
.src/main/scala/customer/view/CustomerByNameView.scala
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/scala/customer/view/CustomerByNameView.scala[tag=process-events]
----
<1> Extends the generated `AbstractCustomerByNameView`, which extends link:{attachmentsdir}/scala-api/com/akkaserverless/scalasdk/view/View.html[`View` {tab-icon}, window="new"].
<2> Defines the initial, empty, state that is used before any updates.
<3> One method for each event.

NOTE: This type of update transformation is a natural fit for Events emitted by an Event Sourced Entity, but it can also be used for Value Entities. For example, if the View representation is different from the Entity state you might want to transform it before presenting the View to the client.

Expand All @@ -251,7 +254,7 @@ Scala::
[source,scala,indent=0]
.src/main/scala/customer/Main.scala
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/scala/customer/Main.scala[tag=register]
----

[#topic-view]
Expand All @@ -274,8 +277,9 @@ Scala::
[source,proto,indent=0]
.src/main/proto/customer/view/customer_view.proto
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/proto/customer/view/customer_view.proto[tags=declarations;service-topic]
----
<1> This is the only difference from <<event-sourced-entity>>.

[#transform-results]
== How to transform results
Expand Down Expand Up @@ -379,7 +383,7 @@ Scala::
[source,scala,indent=0]
.src/main/scala/customer/Main.scala
----
TODO
include::example$scala-eventsourced-customer-registry/src/main/scala/customer/MainWithCustomViewId.scala[tag=register]
----

The View definitions are stored and validated when a new version is deployed. There will be an error message if the changes are not compatible.
Expand Down
47 changes: 47 additions & 0 deletions samples/scala-eventsourced-customer-registry/.scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
version = 3.0.3

style = defaultWithAlign

docstrings.style = Asterisk
indentOperator.preset = spray
maxColumn = 120
rewrite.rules = [RedundantParens, SortImports, AvoidInfix]
unindentTopLevelOperators = true
align.tokens = [{code = "=>", owner = "Case"}]
align.openParenDefnSite = false
align.openParenCallSite = false
optIn.configStyleArguments = false
danglingParentheses.preset = false
spaces.inImportCurlyBraces = true
newlines.afterCurlyLambda = preserve
rewrite.neverInfix.excludeFilters = [
and
min
max
until
to
by
eq
ne
"should.*"
"contain.*"
"must.*"
in
ignore
be
taggedAs
thrownBy
synchronized
have
when
size
only
noneOf
oneElementOf
noElementsOf
atLeastOneElementOf
atMostOneElementOf
allElementsOf
inOrderElementsOf
theSameElementsAs
]
73 changes: 73 additions & 0 deletions samples/scala-eventsourced-customer-registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Implementing a Counter as an Event Sourced Entity


## Building and running unit tests

To compile and test the code from the command line, use

```shell
sbt test
```



## Running Locally

In order to run your application locally, you must run the Akka Serverless proxy. The included `docker compose` file contains the configuration required to run the proxy for a locally running application.
It also contains the configuration to start a local Google Pub/Sub emulator that the Akka Serverless proxy will connect to.
To start the proxy, run the following command from this directory:

```
docker-compose up
```

To start the application locally, use the following command:

```
sbt run
```

For further details see [Running a service locally](https://developer.lightbend.com/docs/akka-serverless/developing/running-service-locally.html) in the documentation.

## Exercise the service

With both the proxy and your application running, any defined endpoints should be available at `http://localhost:9000`. In addition to the defined gRPC interface, each method has a corresponding HTTP endpoint. Unless configured otherwise (see [Transcoding HTTP](https://docs.lbcs.dev/js-services/proto.html#_transcoding_http)), this endpoint accepts POST requests at the path `/[package].[entity name]/[method]`.

* Create a customer with:
```
grpcurl --plaintext -d '{"customer_id": "wip", "email": "wip@example.com", "name": "Very Important", "address": {"street": "Road 1", "city": "The Capital"}}' localhost:9000 customer.api.CustomerService/Create
```
* Retrieve the customer:
```
grpcurl --plaintext -d '{"customer_id": "wip"}' localhost:9000 customer.api.CustomerService/GetCustomer
```
* Query by name:
```
grpcurl --plaintext -d '{"customer_name": "Very Important"}' localhost:9000 customer.view.CustomerByName/GetCustomers
```
* Change name:
```
grpcurl --plaintext -d '{"customer_id": "wip", "new_name": "Most Important"}' localhost:9000 customer.api.CustomerService/ChangeName
```
* Change address:
```
grpcurl --plaintext -d '{"customer_id": "wip", "new_address": {"street": "Street 1", "city": "The City"}}' localhost:9000 customer.api.CustomerService/ChangeAddress
```

## Deploying

To deploy your service, install the `akkasls` CLI as documented in
[Setting up a local development environment](https://developer.lightbend.com/docs/akka-serverless/setting-up/)
and configure a Docker Registry to upload your docker image to.

You will need to set the `docker.username` system property when starting sbt to be able to publish the image, for example `sbt -Ddocker.username=myuser docker:publish`.

If you are publishing to a different registry than docker hub, you will also need to specify what registry using the system property `docker.registry`.

Refer to
[Configuring registries](https://developer.lightbend.com/docs/akka-serverless/projects/container-registries.html)
for more information on how to make your docker image available to Akka Serverless.

Finally, you can use the [Akka Serverless Console](https://console.akkaserverless.com)
to create an Akka Serverless project and then deploy your service into it
through the `akkasls` CLI or via the web interface.
38 changes: 38 additions & 0 deletions samples/scala-eventsourced-customer-registry/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name := "eventsourced-customer-registry"

organization := "com.akkaseverless.samples"
organizationHomepage := Some(url("https://akkaserverless.com"))
licenses := Seq(("CC0", url("https://creativecommons.org/publicdomain/zero/1.0")))

scalaVersion := "2.13.6"

enablePlugins(AkkaserverlessPlugin, JavaAppPackaging, DockerPlugin)
dockerBaseImage := "docker.io/library/adoptopenjdk:11-jre-hotspot"
dockerUsername := sys.props.get("docker.username")
dockerRepository := sys.props.get("docker.registry")
ThisBuild / dynverSeparator := "-"

Compile / scalacOptions ++= Seq(
"-target:11",
"-deprecation",
"-feature",
"-unchecked",
"-Xlog-reflective-calls",
"-Xlint")
Compile / javacOptions ++= Seq("-Xlint:unchecked", "-Xlint:deprecation", "-parameters" // for Jackson
)

Test / parallelExecution := false
Test / testOptions += Tests.Argument("-oDF")
Test / logBuffered := false

Compile / run := {
// needed for the proxy to access the user function on all platforms
sys.props += "akkaserverless.user-function-interface" -> "0.0.0.0"
(Compile / run).evaluated
}
run / fork := false
Global / cancelable := false // ctrl-c

libraryDependencies ++= Seq("org.scalatest" %% "scalatest" % "3.2.7" % Test)

18 changes: 18 additions & 0 deletions samples/scala-eventsourced-customer-registry/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3"
services:
akka-serverless-proxy:
image: gcr.io/akkaserverless-public/akkaserverless-proxy:0.7.1
command: -Dconfig.resource=dev-mode.conf -Dakkaserverless.proxy.eventing.support=google-pubsub-emulator
ports:
- "9000:9000"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
USER_FUNCTION_HOST: ${USER_FUNCTION_HOST:-host.docker.internal}
USER_FUNCTION_PORT: ${USER_FUNCTION_PORT:-8080}
PUBSUB_EMULATOR_HOST: gcloud-pubsub-emulator
gcloud-pubsub-emulator:
image: gcr.io/google.com/cloudsdktool/cloud-sdk:341.0.0
command: gcloud beta emulators pubsub start --project=test --host-port=0.0.0.0:8085
ports:
- 8085:8085
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.4.9
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
addSbtPlugin("com.akkaserverless" % "sbt-akkaserverless" % System.getProperty("akkaserverless-sdk.version", "0.8.0"))
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.8.1")
addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.2")
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2021 Lightbend Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package customer.api;

// tag::declarations[]

import "google/protobuf/empty.proto";
import "akkaserverless/annotations.proto";

// end::declarations[]

// tag::view[]
message Customer {
string customer_id = 1 [(akkaserverless.field).entity_key = true];
string email = 2;
string name = 3;
Address address = 4;
}

message Address {
string street = 1;
string city = 2;
}
// end::view[]

message GetCustomerRequest {
string customer_id = 1 [(akkaserverless.field).entity_key = true];
}

message ChangeNameRequest {
string customer_id = 1 [(akkaserverless.field).entity_key = true];
string new_name = 2;
}

message ChangeAddressRequest {
string customer_id = 1 [(akkaserverless.field).entity_key = true];
Address new_address = 2;
}

service CustomerService {
option (akkaserverless.service) = {
type: SERVICE_TYPE_ENTITY
component: "customer.domain.CustomerEntity"
};

rpc Create(Customer) returns (google.protobuf.Empty) {}

rpc ChangeName(ChangeNameRequest) returns (google.protobuf.Empty) {}

rpc ChangeAddress(ChangeAddressRequest) returns (google.protobuf.Empty) {}

rpc GetCustomer(GetCustomerRequest) returns (Customer) {}
}
Loading