Skip to content

Commit

Permalink
chore: renaming Workflows (#1687)
Browse files Browse the repository at this point in the history
* chore: renaming Workflows

* use TypeId and Id annotations in Workflows

* migrate to TypeId and Id in samples

* Update docs/src/modules/java/pages/event-sourced-entities.adoc

Co-authored-by: Andrzej Ludwikowski <[email protected]>

---------

Co-authored-by: Andrzej Ludwikowski <[email protected]>
  • Loading branch information
octonato and aludwiko authored Jun 22, 2023
1 parent bcd9660 commit 28fa345
Show file tree
Hide file tree
Showing 91 changed files with 865 additions and 667 deletions.
2 changes: 1 addition & 1 deletion docs/src/modules/java/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*** xref:java:spring-boot-integration.adoc[Spring Boot Integration]
*** xref:java:value-entity.adoc[Implementing Value Entities]
*** xref:java:event-sourced-entities.adoc[Implementing Event Sourced Entities]
*** xref:java:workflow-entities.adoc[Implementing Workflow Entities]
*** xref:java:workflows.adoc[Implementing Workflows]
// *** xref:java:replicated-entity.adoc[Implementing Replicated Entities] to be implemented
*** xref:java:views.adoc[Implementing Views]
*** xref:java:actions.adoc[Implementing Actions]
Expand Down
6 changes: 3 additions & 3 deletions docs/src/modules/java/pages/event-sourced-entities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ Let's have a look at what our shopping cart entity will look like for the first
include::example$java-spring-eventsourced-shopping-cart/src/main/java/com/example/shoppingcart/ShoppingCartEntity.java[tag=class]
----
<1> Create a class that extends `EventSourcedEntity<S, E>`, where `S` is the state type this entity will store (i.e. `ShoppingCart`) and `E` is the top type for the events it emits (i.e. `ShoppingCartEvent`).
<2> Annotate such class with `@EntityKey` and pass the name of the key that will be used as the entity unique identifier.
<3> Make sure to annotate such class with `@EntityType` and pass a unique name for this entity type.
<2> Annotate such class with `@Id` and pass the name of the id that will be used as the entity instance unique identifier.
<3> Make sure to annotate such class with `@TypeId` and pass a unique identifier for this entity type.
<4> Use Spring's RequestMapping annotation to define the route to your entity.


NOTE: The EntityKey `cartId` must match a path parameter (i.e. `cartId`) and such value needs to be unique per entity. On the other hand, the EntityType `shopping-cart` is common for all instances of this entity but must be stable - cannot be changed after a production deploy - and unique across the different entity types.
NOTE: The `@Id` value `cartId` must match a path parameter (i.e. `cartId`) and such value needs to be unique per entity. On the other hand, the `@TypeId` value `shopping-cart` is common for all instances of this entity but must be stable - cannot be changed after a production deploy - and unique across the different entity types.

=== Updating state

Expand Down
6 changes: 3 additions & 3 deletions docs/src/modules/java/pages/getting-started.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ that receives commands to increase the counter.
----
include::example$java-spring-valueentity-counter/src/main/java/com/example/CounterEntity.java[tags=declarations;increase;close]
----
<1> Every Entity must be annotated with `@EntityType` with a stable name. This name should be unique among the different existing entities within a Kalix application.
<2> The `@EntityKey` value should be unique per entity and map to some field being received on the route path, in this example it's the `counter_id`.
<1> Every Entity must be annotated with `@TypeId` with a stable identifier. This identifier should be unique among the different existing entities within a Kalix application.
<2> The `@Id` value should be unique per entity and map to some field being received on the route path, in this example it's the `counter_id`.
<3> The `CounterEntity` class should extend `kalix.javasdk.valueentity.ValueEntity`.
<4> The initial state of each counter is defined as 0.
<5> The method is accessible as a POST endpoint on `/counter/\{counter_id\}/increase`, where `counter_id` will be its unique identifier.
Note that it matches the `entityKey` value.
Note that it matches the `@Id` value.
<6> Its `increaseBy` method receives a `Number` as input and increases the counter by adding it to the current state.
<7> Finally, using the Effect API, we instruct Kalix to persist the new state, and we build a reply.

Expand Down
6 changes: 3 additions & 3 deletions docs/src/modules/java/pages/value-entity.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ Let us start by showing how to create the Value Entity:
----
include::example$java-spring-valueentity-counter/src/main/java/com/example/CounterEntity.java[tags=declarations]
----
<1> Every Entity must be annotated with `@EntityType` with a stable name. This name should be unique among the different existing entities within a Kalix application.
<2> The `@EntityKey` value should be unique per entity and map to some field being received on the route path, in this example it's the `counter_id`.
<1> Every Entity must be annotated with `@TypeId` with a stable identifier. This identifier should be unique among the different existing entities within a Kalix application.
<2> The `@Id` value should be unique per entity and map to some field being received on the route path, in this example it's the `counter_id`.
<3> The `CounterEntity` class should extend `kalix.javasdk.valueentity.ValueEntity`.
<4> The initial state of each counter is defined as 0.

Expand All @@ -62,7 +62,7 @@ include::example$java-spring-valueentity-counter/src/main/java/com/example/Count
<5> `plusOne` increases the counter by adding 1 to the current state.
<6> Finally, using the Effect API, you instruct Kalix to persist the new state, and build a reply with the wrapper object.

NOTE: The `counter_id` parameter matches the `entityKey` value. Also, for this example, we have opted to always repeat the common route `/counter/\{counter_id\}` for each command but a simpler option could be to use a `@RequestMethod("/counter/\{counter_id\}")` at class level.
NOTE: The `counter_id` parameter matches the `@Id` value. Also, for this example, we have opted to always repeat the common route `/counter/\{counter_id\}` for each command but a simpler option could be to use a `@RequestMethod("/counter/\{counter_id\}")` at class level.

[#deleting-state]
=== Deleting state
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
= Implementing Workflow Entities
:page-aliases: spring:workflow.adoc, spring:workflow-entities.adoc
= Implementing Workflows
:page-aliases: spring:workflow.adoc, spring:workflow-entities.adoc, java:workflow-entities.adoc
:sample-url: https://github.com/lightbend/kalix-jvm-sdk/tree/main/samples/java-spring-transfer-workflow

include::ROOT:partial$include.adoc[]

Implementing a business transaction that spans multiple services is one of the major challenges in distributed systems implementation. Fortunately, with Kalix ecosystem and concepts like https://docs.kalix.io/spring/actions-publishing-subscribing.html[Subscription], it's very easy to build an event driven choreography that covers a complex business process. A https://microservices.io/patterns/data/saga.html[Saga pattern] that generalizes this solution was never so easy to implement. However, for some use cases a different flavor of the Saga pattern, an orchestration, might be more suitable. That was the main driver behind a Kalix component type called a Workflow Entity. From a high-level perspective it joins the power of https://docs.kalix.io/concepts/state-model.html[Kalix Entities state model] (durability, consistency guaranties) and https://docs.kalix.io/spring/actions.html[Kalix Actions] ability to call other components and services. You can model your business process in a single place and the Workflow Entity will keep it running or rollback in case of a failure.
Implementing a business transaction that spans multiple services is one of the major challenges in distributed systems implementation. Fortunately, with Kalix ecosystem and concepts like https://docs.kalix.io/spring/actions-publishing-subscribing.html[Subscription], it's very easy to build an event driven choreography that covers a complex business process. A https://microservices.io/patterns/data/saga.html[Saga pattern] that generalizes this solution was never so easy to implement. However, for some use cases a different flavor of the Saga pattern, an orchestration, might be more suitable. That was the main driver behind a Kalix component type called a Workflow. From a high-level perspective it joins the power of https://docs.kalix.io/concepts/state-model.html[Kalix Entities state model] (durability, consistency guaranties) and https://docs.kalix.io/spring/actions.html[Kalix Actions] ability to call other components and services. You can model your business process in a single place and the Workflow will keep it running or rollback in case of a failure.

== Modeling state

Expand Down Expand Up @@ -34,29 +34,29 @@ include::example$java-spring-transfer-workflow/src/main/java/com/example/transfe

Now that we have our workflow state defined, the remaining tasks can be summarized as follows:

- declare your entity and pick an entity type and key (it needs to be unique as it will be used for sharding purposes);
- define an access point (i.e. a route path) to your entity;
- declare your workflow and pick a workflow type and key (it needs to be unique as it will be used for sharding purposes);
- define an access point (i.e. a route path) to your workflow;
- implement endpoint(s) to interact with the workflow (e.g. to start a workflow, or provide additional data) or retrieve its current state;
- provide a workflow definition with all possible steps and transitions between them.

Let's have a look at what our transfer workflow entity will look like for the first 2 points from the above list:
Let's have a look at what our transfer workflow will look like for the first 2 points from the above list:

[source,java,indent=0]
.src/main/java/com/example/transfer/TransferWorkflow.java
----
include::example$java-spring-transfer-workflow/src/main/java/com/example/transfer/TransferWorkflow.java[tag=class]
----
<1> Create a class that extends `WorkflowEntity<S>`, where `S` is the state type this entity will store (i.e. `TransferState`).
<2> Make sure to annotate such class with `@EntityType` and pass a unique name for this entity type.
<3> Annotate such class with `@EntityKey` and pass the name of the key that will be used as the entity unique identifier.
<4> Use Spring's RequestMapping annotation to define the route to your entity.
<1> Create a class that extends `Workflow<S>`, where `S` is the state type this workflow will store (i.e. `TransferState`).
<2> Make sure to annotate such class with `@TypeId` and pass a unique identifier for this workflow type.
<3> Annotate such class with `@Id` and pass the name of the key that will be used as the workflow instance unique identifier.
<4> Use Spring's RequestMapping annotation to define the route to your workflow.


NOTE: The EntityKey `transferId` must match a path parameter (i.e. `transferId`) and such value needs to be unique per entity. On the other hand, the EntityType `transfer` is common for all instances of this workflow entity but must be stable - cannot be changed after a production deploy - and unique across the different entity types.
NOTE: The `@Id` value `transferId` must match a path parameter (i.e. `transferId`) and such value needs to be unique per workflow. On the other hand, the `@TypeId` value `transfer` is common for all instances of this workflow but must be stable - cannot be changed after a production deploy - and unique across the different workflow types.

== Starting workflow

Having created the basis of our workflow entity, we will now define how to launch a workflow with a command handler. In the example below, we define a new endpoint that will accept `StartTransfer` command and return an `Effect` to start a workflow by providing a transition to the first step. Also, we will update the state with an initial value.
Having created the basis of our workflow, we will now define how to launch a workflow with a command handler. In the example below, we define a new endpoint that will accept `StartTransfer` command and return an `Effect` to start a workflow by providing a transition to the first step. Also, we will update the state with an initial value.


[source,java,indent=0]
Expand All @@ -71,7 +71,7 @@ include::example$java-spring-transfer-workflow/src/main/java/com/example/transfe
<5> With the `transitionTo` method, we inform that the name of the first step is `"withdraw"` and the input for this step is a `Withdraw` object.
<6> The last instruction is to inform the caller that the workflow was successfully started.

IMPORTANT: For simplicity purposes, we are reusing the internal `Transfer` record as a request body. This should be a separate class and our domain state model shouldn't be exposed as an entity public `API`.
IMPORTANT: For simplicity purposes, we are reusing the internal `Transfer` record as a request body. This should be a separate class and our domain state model shouldn't be exposed as a public `API`.

== Workflow definition

Expand All @@ -93,7 +93,7 @@ include::example$java-spring-transfer-workflow/src/main/java/com/example/transfe

== Retrieving state

To have access to the current state of the workflow entity we can use `currentState()` (similar to other entities). However, if this is the first command we are receiving for this workflow entity, the state will be `null`. We can change it by overriding the `emptyState` method. The following example shows the implementation of the read-only command handler (accessed through `GET /transfer/transferId`):
To have access to the current state of the workflow we can use `currentState()` (similar to other entities). However, if this is the first command we are receiving for this workflow, the state will be `null`. We can change it by overriding the `emptyState` method. The following example shows the implementation of the read-only command handler (accessed through `GET /transfer/transferId`):

[source,java,indent=0]
.src/main/java/com/example/transfer/TransferWorkflow.java
Expand All @@ -110,7 +110,7 @@ A full transfer workflow source code is available {sample-url}[here, {tab-icon},

== Error handling

Design for failure is one of the key attributes of all Kalix components. Workflow Entity has the richest set of configurations from all of them. It's essential to build robust and reliable solutions.
Design for failure is one of the key attributes of all Kalix components. Workflow has the richest set of configurations from all of them. It's essential to build robust and reliable solutions.

=== Timeouts

Expand Down Expand Up @@ -151,7 +151,7 @@ NOTE: In case of a workflow timeout one last failover step can be performed. Tra

=== Compensation

The idea behind the Workflow Entity error handling is that workflows should only fail due to unknown errors during execution. In general, you should always write your workflows so that they do not fail on any known edge cases. If you expect an error, it's better to be explicit about it, possibly with your domain types. Based on this information and the flexible Workflow Entity API you can define a compensation for any workflow step.
The idea behind the Workflow error handling is that workflows should only fail due to unknown errors during execution. In general, you should always write your workflows so that they do not fail on any known edge cases. If you expect an error, it's better to be explicit about it, possibly with your domain types. Based on this information and the flexible Workflow API you can define a compensation for any workflow step.

[source,java,indent=0]
.src/main/java/com/example/transfer/TransferWorkflow.java
Expand Down
20 changes: 10 additions & 10 deletions docs/src/modules/java/partials/entity-keys.adoc
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@

== Identifying the Entity

In order to interact with an Entity in Kalix, we need to assign an *entity type* and one or more *entity keys*:
In order to interact with an Entity in Kalix, we need to assign an *type id* and one or more instance *ids*:

* *entity type* is a unique identifier for all entities of a given type. To define the entity type, the entity class must be annotated with `@EntityType` and have a unique and stable name assigned.
* *entity key*, on the other hand, is unique per instance. In most cases, the entity key is passed as a path parameter of a REST request. The exception to the rule is when we request Kalix to auto-generate a key for us. In such a case, Kalix won't try to extract the key from the endpoint path.
* *type id* is a unique identifier for all entities of a given type. To define the entity type id, the entity class must be annotated with `@TypeId` and have a unique and stable identifier assigned.
* *id*, on the other hand, is unique per instance. In most cases, the entity id is passed as a path parameter of a REST request. The exception to the rule is when we request Kalix to auto-generate a id for us. In such a case, Kalix won't try to extract the id from the endpoint path.

The entity key can be defined in different ways, as detailed below.
The entity id can be defined in different ways, as detailed below.

=== Single keys

The most common use is to annotate the class with `@EntityKey` and assign one path variable name to it.
For instance, `@EntityKey("id")` will instruct Kalix to look up a matching path variable. For an endpoint defined with `@RequestMapping("/users/\{id}")`, Kalix will extract whatever path segment is used to replace `\{id}` and treat it as the Entity unique identifier.
The most common use is to annotate the class with `@Id` and assign one path variable name to it.
For instance, `@Id("id")` will instruct Kalix to look up a matching path variable. For an endpoint defined with `@RequestMapping("/users/\{id}")`, Kalix will extract whatever path segment is used to replace `\{id}` and treat it as the Entity unique identifier.

=== Composite keys

It's also possible to have composite keys. For example, `@EntityKey({"groupId", "id"})` defines a composite key made of `groupId` and `id`. In such a case, the endpoints for this entity will need to have both path variables, e.g.: `@RequestMapping("/users/\{groupId}/\{id}")`.
It's also possible to have composite keys. For example, `@Id({"groupId", "id"})` defines a composite key made of `groupId` and `id`. In such a case, the endpoints for this entity will need to have both path variables, e.g.: `@RequestMapping("/users/\{groupId}/\{id}")`.

=== Generated keys

Finally, you can ask Kalix to generate an Entity key, this is typically useful when creating an Entity, and the key is a surrogate key. To indicate to Kalix that an Entity key should be generated rather than extracted from the path, be sure to annotate the corresponding command method with `@GenerateEntityKey`. Typically, an Entity has only one method annotated with `@GenerateEntityKey`. The one that creates the Entity. All other methods will have `@EntityKey` annotation in order to extract the surrogate key from the endpoint path.
Finally, you can ask Kalix to generate an unique identifier, this is typically useful when creating an Entity, and the id is a surrogate id. To indicate to Kalix that an Entity id should be generated rather than extracted from the path, be sure to annotate the corresponding command method with `@GenerateId`. Typically, an Entity has only one method annotated with `@GenerateId`. The one that creates the Entity. All other methods will have `@Id` annotation in order to extract the surrogate id from the endpoint path.

It will often be necessary to access the generated entity key from inside the entities code. This can be done using the link:{attachmentsdir}/api/kalix/javasdk/EntityContext.html#entityId()[`EntityContext.entityId`{tab-icon},window="new"] method.
It will often be necessary to access the generated entity id from inside the entities code. This can be done using the link:{attachmentsdir}/api/kalix/javasdk/EntityContext.html#entityId()[`EntityContext.entityId`{tab-icon},window="new"] method.

NOTE: Kalix generates a UUID version 4 (random) keys. Only version 4 UUIDs are currently supported for generated Entity keys.
NOTE: Kalix generates a UUID version 4 (random) keys. Only version 4 UUIDs are currently supported for generated Entity identifiers.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@

// tag::customer[]
import kalix.javasdk.valueentity.ValueEntity;
import kalix.javasdk.annotations.EntityKey;
import kalix.javasdk.annotations.EntityType;
import kalix.javasdk.annotations.Id;
import kalix.javasdk.annotations.TypeId;
import org.springframework.web.bind.annotation.*;
import io.grpc.Status;
import customer.domain.Address;
import customer.domain.Customer;

@EntityType("customer") // <1>
@EntityKey("customer_id") // <2>
@TypeId("customer") // <1>
@Id("customer_id") // <2>
@RequestMapping("/customer/{customer_id}") // <3>
public class CustomerEntity extends ValueEntity<Customer> { // <4>

Expand Down
Loading

0 comments on commit 28fa345

Please sign in to comment.