Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java-eventing-shopping-cart with codegen #282

Merged
merged 2 commits into from Sep 7, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions docs/src/modules/java/pages/actions.adoc
Expand Up @@ -16,14 +16,14 @@ The class implementing an Action needs to be annotated with the `@Action` annota

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/product/ToProductPopularityAction.java[tag=annotation]
include::example$java-eventing-shopping-cart/src/main/java/shopping/product/actions/ToProductPopularityServiceAction.java[tag=annotation]
----

Action methods implementing services require the `@Handler` annotation and may have `ActionContext` as their second parameter.

[source,java,indent=2]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/product/ToProductPopularityAction.java[tag=methods]
include::example$java-eventing-shopping-cart/src/main/java/shopping/product/actions/ToProductPopularityServiceAction.java[tag=methods]
----

To connect the Protobuf service definition with the implementation as an Action, register the implementing class with Akka Serverless:
Expand All @@ -41,19 +41,19 @@ The publisher (or source) may publish an arbitrary number of replies.

=== Forwarding a call from an Action

Action methods that want to forward calls to other service methods use `Reply<T>` as return type. With `Reply<T>` the method can choose to
Action methods that want to forward calls to other service methods use `Effect<T>` as return type. With `Effect<T>` the method can choose to

- reply with a value (`Reply.message`)
- send no reply (`Reply.noReply`)
- forward the call (`Reply.forward`)
- reply with a value (`effects().reply(T)`)
- send no reply (`effects().noReply()`)
- forward the call (`effects().forward()`)

To forward to a different method, look up the target method via the `ActionContext` by specifying the full Protobuf service name, method name and passing the parameter type's class.

[source,java,indent=2]
----
include::java:example$java-eventing-shopping-cart/src/main/java/shopping/product/ToProductPopularityAction.java[tag=forwardRemoved]
include::java:example$java-eventing-shopping-cart/src/main/java/shopping/product/actions/ToProductPopularityServiceAction.java[tag=forwardRemoved]
----
<1> Use `ActionReply<Empty>` as return type and accept an `ActionContext` as second parameter
<1> Use `Effect<Empty>` as return type
<2> Create the parameter the target method accepts
<3> Use the fully-qualified gRPC service name of the target method
<4> Specify the Protobuf rpc method name
Expand Down
30 changes: 8 additions & 22 deletions docs/src/modules/java/pages/eventsourced.adoc
Expand Up @@ -9,7 +9,7 @@ Create an Event Sourced Entity by annotating it with the link:{attachmentsdir}/a

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=class]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=class]
----

The link:{attachmentsdir}/api/com/akkaserverless/javasdk/eventsourcedentity/EventSourcedEntity.html#entityType()[`entityType` {tab-icon}, window="new"] provides a namespace for journal events. Use the simple name for the Entity class. To have a more unique reference, the example above uses `eventsourced-shopping-cart`.
Expand All @@ -34,7 +34,7 @@ Each Entity should store its state locally in a mutable variable, either a mutab

[source,java,indent=4]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=state]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=state]
----

== Constructing
Expand All @@ -43,7 +43,7 @@ Akka Serverless constructs instances of the Event Sourced Entity class on demand

[source,java,indent=4]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=constructor]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=constructor]
----

== Handling commands
Expand All @@ -56,7 +56,7 @@ The following shows the implementation of the `GetCart` command handler:

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=getCart]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=getCart]
----

== Emitting events
Expand All @@ -67,7 +67,7 @@ IMPORTANT: The **only** way for a command handler to modify Entity state is by e

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=addItem]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=addItem]
----

This command handler also validates the command, ensuring the quantity of items added is greater than zero. Invoking link:{attachmentsdir}/api/com/akkaserverless/javasdk/ClientActionContext.html#fail(java.lang.String)[`context.fail` {tab-icon}, window="new"] fails the command - this method throws - no need to explicitly throw an exception.
Expand All @@ -91,28 +91,14 @@ The following example shows an event handler for the `ItemAdded` event with a ut

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=itemAdded]
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/domain/ShoppingCartEntity.java[tag=itemAdded]
----

== Producing and handling snapshots
== Snapshots

Snapshots are an important optimization for Event Sourced Entities that emit many events. Rather than reading the entire journal upon loading or restart, Akka Serverless can initiate them from a snapshot.

To produce a snapshot, declare a method annotated with link:{attachmentsdir}/api/com/akkaserverless/javasdk/eventsourcedentity/Snapshot.html[`@Snapshot` {tab-icon}, window="new"]. It takes a context class of type link:{attachmentsdir}/api/com/akkaserverless/javasdk/eventsourcedentity/SnapshotContext.html[`SnapshotContext` {tab-icon}, window="new"], and must return a snapshot of the current state in serializable form.

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=snapshot]
----

When the Event Sourced Entity is loaded again, the snapshot will be loaded before any other events are received, and passed to a snapshot handler. Snapshot handlers are declared by annotating a method with link:{attachmentsdir}/api/com/akkaserverless/javasdk/eventsourcedentity/SnapshotHandler.html[`@SnapshotHandler` {tab-icon}, window="new"], and it can take a context class of type link:{attachmentsdir}/api/com/akkaserverless/javasdk/eventsourcedentity/SnapshotContext.html[`SnapshotContext` {tab-icon}, window="new"].

Multiple snapshot handlers may be defined to handle different types of snapshots. The type matching is done in the same way as for events.

[source,java,indent=0]
----
include::example$java-eventing-shopping-cart/src/main/java/shopping/cart/ShoppingCartEntity.java[tag=handleSnapshot]
----
Snapshots are handled automatically by Akka Serverless without any specific code required. By default, a snapshot is taken for every 100:th event emitted. Tuning how often a snapshot of the state is taken can be configured using config or programmatically handing a `EventSourcedEntityOptions` to the provider when registering the entity.

== Registering the Entity

Expand Down
@@ -1,42 +1,39 @@
/*
* 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.
/* This code was generated by Akka Serverless tooling.
* As long as this file exists it will not be re-generated.
* You are free to make changes to this file.
*/

package shopping;
package shopping.cart.domain;

import com.akkaserverless.javasdk.testkit.junit.AkkaServerlessTestkitResource;
import shopping.cart.api.ShoppingCartApi;
import shopping.cart.api.ShoppingCartServiceClient;
import com.google.protobuf.Empty;
import org.junit.ClassRule;
import org.junit.Test;

import shopping.Main;
import shopping.cart.api.ShoppingCartApi;
import shopping.cart.api.ShoppingCartServiceClient;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static java.util.concurrent.TimeUnit.*;
import static org.junit.Assert.*;

public class ShoppingCartIntegrationTest {
// Example of an integration test calling our service via the Akka Serverless proxy
// Run all test classes ending with "IntegrationTest" using `mvn verify -Pit`
public class ShoppingCartEntityIntegrationTest {

/**
* The test kit starts both the service container and the Akka Serverless proxy.
*/
@ClassRule
public static final AkkaServerlessTestkitResource testkit =
new AkkaServerlessTestkitResource(Main.createAkkaServerless());
new AkkaServerlessTestkitResource(Main.createAkkaServerless());

/**
* Use the generated gRPC client to call the service through the Akka Serverless proxy.
*/
private final ShoppingCartServiceClient client;

public ShoppingCartIntegrationTest() {
this.client =
ShoppingCartServiceClient.create(testkit.getGrpcClientSettings(), testkit.getActorSystem());
public ShoppingCartEntityIntegrationTest() {
client = ShoppingCartServiceClient.create(testkit.getGrpcClientSettings(), testkit.getActorSystem());
}

ShoppingCartApi.Cart getCart(String cartId) throws Exception {
Expand All @@ -56,7 +53,7 @@ void addItem(String cartId, String productId, String name, int quantity) throws
.setQuantity(quantity)
.build())
.toCompletableFuture()
.get();
.get(2, SECONDS);
}

void removeItem(String cartId, String productId, int quantity) throws Exception {
Expand All @@ -68,7 +65,7 @@ void removeItem(String cartId, String productId, int quantity) throws Exception
.setQuantity(quantity)
.build())
.toCompletableFuture()
.get();
.get(2, SECONDS);
}

ShoppingCartApi.LineItem item(String productId, String name, int quantity) {
Expand Down Expand Up @@ -120,4 +117,4 @@ public void removeItemsFromCart() throws Exception {
cart2.getItemsList(),
List.of(item("b", "Banana", 2)));
}
}
}
@@ -0,0 +1,60 @@
/* This code was generated by Akka Serverless tooling.
* As long as this file exists it will not be re-generated.
* You are free to make changes to this file.
*/
package shopping.product.domain;

import com.akkaserverless.javasdk.testkit.junit.AkkaServerlessTestkitResource;
import com.google.protobuf.Empty;
import org.junit.ClassRule;
import org.junit.Test;
import shopping.Main;
import shopping.product.api.ProductPopularityApi;
import shopping.product.api.ProductPopularityServiceClient;

import static java.util.concurrent.TimeUnit.*;

// Example of an integration test calling our service via the Akka Serverless proxy
// Run all test classes ending with "IntegrationTest" using `mvn verify -Pit`
public class ProductPopularityValueEntityIntegrationTest {

/**
* The test kit starts both the service container and the Akka Serverless proxy.
*/
@ClassRule
public static final AkkaServerlessTestkitResource testkit =
new AkkaServerlessTestkitResource(Main.createAkkaServerless());

/**
* Use the generated gRPC client to call the service through the Akka Serverless proxy.
*/
private final ProductPopularityServiceClient client;

public ProductPopularityValueEntityIntegrationTest() {
client = ProductPopularityServiceClient.create(testkit.getGrpcClientSettings(), testkit.getActorSystem());
}

/*
@Test
public void increaseOnNonExistingEntity() throws Exception {
// TODO: set fields in command, and provide assertions to match replies
// client.increase(ProductPopularityApi.IncreasePopularity.newBuilder().build())
// .toCompletableFuture().get(2, SECONDS);
}

@Test
public void decreaseOnNonExistingEntity() throws Exception {
// TODO: set fields in command, and provide assertions to match replies
// client.decrease(ProductPopularityApi.DecreasePopularity.newBuilder().build())
// .toCompletableFuture().get(2, SECONDS);
}

@Test
public void getPopularityOnNonExistingEntity() throws Exception {
// TODO: set fields in command, and provide assertions to match replies
// client.getPopularity(ProductPopularityApi.GetProductPopularity.newBuilder().build())
// .toCompletableFuture().get(2, SECONDS);
}

*/
}