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

chore: aligning view store sample #118

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 0 additions & 20 deletions docs/src/modules/java/pages/key-value-entities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,23 +161,3 @@ include::example$key-value-counter/src/test/java/com/example/CounterIntegrationT
<5> Explicitly request current value of `bar`. It should be `1`.

NOTE: The integration tests in samples can be run using `mvn integration-test`.

== Exposing entities directly

include::partial$component-endpoint.adoc[]

=== API

The entity is exposed at a fixed path:

[source]
----
/akka/v1.0/entity/<component id>/<entity id>/<method>
----

In our counter example that is:

[source,shell]
----
curl localhost:9000/akka/v1.0/entity/counter/foo/get
----
23 changes: 0 additions & 23 deletions docs/src/modules/java/pages/views.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -318,26 +318,3 @@ include::example$key-value-customer-registry/src/test/java/customer/application/
Views are not replicated directly in the same way as for example xref:event-sourced-entities.adoc#_replication[Event Sourced Entity replication]. A View is built from entities in the same service, or another service, in the same region. The entities will replicate all events across regions and identical Views are built in each region.

A View can also be built from a message broker topic, and that could be regional or global depending on how the message broker is configured.

== Exposing views directly

include::partial$component-endpoint.adoc[]

=== API

The view is exposed at a fixed path:

[source]
----
/akka/v1.0/view/<component id>/<method>
----

Taking the sample from the <<value-entity, first section>> as an example, that would be:

[source,shell]
----
curl localhost:9000/akka/v1.0/view/view_customers_by_email/getCustomer \
--header "Content-Type: application/json" \
-XPOST \
--data '{"email":"[email protected]"}'
----
33 changes: 12 additions & 21 deletions samples/view-store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ With your Akka service running, once you have defined endpoints they should be a
Create some products:

```shell
curl localhost:9000/akka/v1.0/entity/product/P123/create \
curl -i localhost:9000/products/P123 \
-XPOST \
--header "Content-Type: application/json" \
--data '{
Expand All @@ -31,7 +31,7 @@ curl localhost:9000/akka/v1.0/entity/product/P123/create \
```

```shell
curl localhost:9000/akka/v1.0/entity/product/P987/create \
curl -i localhost:9000/products/P987 \
-XPOST \
--header "Content-Type: application/json" \
--data '{
Expand All @@ -43,13 +43,13 @@ curl localhost:9000/akka/v1.0/entity/product/P987/create \
Retrieve a product by id:

```shell
curl localhost:9000/akka/v1.0/entity/product/P123/get
curl localhost:9000/products/P123
```

Create a customer:

```shell
curl localhost:9000/akka/v1.0/entity/customer/C001/create \
curl -i localhost:9000/customers/C001 \
-XPOST \
--header "Content-Type: application/json" \
--data '{
Expand All @@ -62,13 +62,13 @@ curl localhost:9000/akka/v1.0/entity/customer/C001/create \
Retrieve a customer by id:

```shell
curl localhost:9000/akka/v1.0/entity/customer/C001/get
curl localhost:9000/customers/C001
```

Create customer orders for the products:

```shell
curl localhost:9000/akka/v1.0/entity/order/O1234/create \
curl -i localhost:9000/orders/O1234 \
-XPOST \
--header "Content-Type: application/json" \
--data '{
Expand All @@ -79,7 +79,7 @@ curl localhost:9000/akka/v1.0/entity/order/O1234/create \
```

```shell
curl localhost:9000/akka/v1.0/entity/order/O5678/create \
curl -i localhost:9000/orders/O5678 \
-XPOST \
--header "Content-Type: application/json" \
--data '{
Expand All @@ -92,38 +92,29 @@ curl localhost:9000/akka/v1.0/entity/order/O5678/create \
Retrieve orders by id:

```shell
curl localhost:9000/akka/v1.0/entity/order/O1234/get
curl localhost:9000/orders/O1234
```

```shell
curl localhost:9000/akka/v1.0/entity/order/O5678/get
curl localhost:9000/orders/O5678
```

Retrieve all product orders for a customer id using a view (with joins):

```shell
curl localhost:9000/akka/v1.0/view/joined-customer-orders/get \
--header "Content-Type: application/json" \
-XPOST \
--data '{ "customerId": "C001" }'
curl localhost:9000/orders/joined-by-customer/C001
```

Retrieve all product orders for a customer id using a view (with joins and nested projection):

```shell
curl localhost:9000/akka/v1.0/view/nested-customer-orders/get \
--header "Content-Type: application/json" \
-XPOST \
--data '{ "customerId": "C001" }'
curl localhost:9000/orders/nested-by-customer/C001
```

Retrieve all product orders for a customer id using a view (with joins and structured projection):

```shell
curl localhost:9000/akka/v1.0/view/structured-customer-orders/get \
--header "Content-Type: application/json" \
-XPOST \
--data '{ "customerId": "C001" }'
curl localhost:9000/orders/structured-by-customer/C001
```

## Deploying
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package store.customer.api;

import akka.http.javadsl.model.HttpResponse;
import akka.javasdk.annotations.Acl;
import akka.javasdk.annotations.http.Get;
import akka.javasdk.annotations.http.HttpEndpoint;
import akka.javasdk.annotations.http.Post;
import akka.javasdk.client.ComponentClient;
import store.customer.application.CustomerEntity;
import store.customer.domain.Customer;

import java.util.concurrent.CompletionStage;

import static akka.javasdk.http.HttpResponses.created;

@HttpEndpoint("/customers")
@Acl(allow = @Acl.Matcher(principal = Acl.Principal.INTERNET))
public class CustomerEndpoint {

private final ComponentClient componentClient;

public CustomerEndpoint(ComponentClient componentClient) {
this.componentClient = componentClient;
}

@Post("/{customerId}")
public CompletionStage<HttpResponse> create(String customerId, Customer customer) {
return componentClient.forEventSourcedEntity(customerId)
.method(CustomerEntity::create)
.invokeAsync(customer)
.thenApply(__ -> created());
}

@Get("/{customerId}")
public CompletionStage<Customer> get(String customerId) {
return componentClient.forEventSourcedEntity(customerId)
.method(CustomerEntity::get)
.invokeAsync();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package store.customer.api;
package store.customer.application;

import akka.Done;
import akka.javasdk.annotations.ComponentId;
import akka.javasdk.eventsourcedentity.EventSourcedEntity;
import store.customer.domain.Address;
import store.customer.domain.Customer;
import store.customer.domain.CustomerEvent;

import static akka.Done.done;
import static store.customer.domain.CustomerEvent.CustomerAddressChanged;
import static store.customer.domain.CustomerEvent.CustomerCreated;
import static store.customer.domain.CustomerEvent.CustomerNameChanged;
Expand All @@ -17,22 +19,20 @@ public ReadOnlyEffect<Customer> get() {
return effects().reply(currentState());
}

public Effect<String> create(Customer customer) {
public Effect<Done> create(Customer customer) {
return effects()
.persist(new CustomerCreated(customer.email(), customer.name(), customer.address()))
.thenReply(__ -> "OK");
.thenReply(__ -> done());
}

public Effect<String> changeName(String newName) {
return effects().persist(new CustomerNameChanged(newName)).thenReply(__ -> "OK");
public Effect<Done> changeName(String newName) {
return effects().persist(new CustomerNameChanged(newName)).thenReply(__ -> done());
}


public Effect<String> changeAddress(Address newAddress) {
return effects().persist(new CustomerAddressChanged(newAddress)).thenReply(__ -> "OK");
public Effect<Done> changeAddress(Address newAddress) {
return effects().persist(new CustomerAddressChanged(newAddress)).thenReply(__ -> done());
}


@Override
public Customer applyEvent(CustomerEvent event) {
return switch (event) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package store.order.api;

import akka.http.javadsl.model.HttpResponse;
import akka.javasdk.annotations.Acl;
import akka.javasdk.annotations.http.Get;
import akka.javasdk.annotations.http.HttpEndpoint;
import akka.javasdk.annotations.http.Post;
import akka.javasdk.client.ComponentClient;
import store.order.application.CreateOrder;
import store.order.application.OrderEntity;
import store.order.domain.Order;
import store.order.view.joined.JoinedCustomerOrdersView;
import store.order.view.nested.CustomerOrders;
import store.order.view.nested.NestedCustomerOrdersView;
import store.order.view.structured.StructuredCustomerOrdersView;

import java.util.concurrent.CompletionStage;

import static akka.javasdk.http.HttpResponses.created;

@HttpEndpoint("/orders")
@Acl(allow = @Acl.Matcher(principal = Acl.Principal.INTERNET))
public class OrderEndpoint {

private final ComponentClient componentClient;

public OrderEndpoint(ComponentClient componentClient) {
this.componentClient = componentClient;
}

@Post("/{orderId}")
public CompletionStage<HttpResponse> create(String orderId, CreateOrder createOrder) {
return componentClient.forKeyValueEntity(orderId)
.method(OrderEntity::create)
.invokeAsync(createOrder)
.thenApply(__ -> created());
}

@Get("/{orderId}")
public CompletionStage<Order> get(String orderId) {
return componentClient.forKeyValueEntity(orderId)
.method(OrderEntity::get)
.invokeAsync();
}

@Get("/joined-by-customer/{customerId}")
public CompletionStage<JoinedCustomerOrdersView.CustomerOrders> joinedByCustomer(String customerId) {
return componentClient.forView()
.method(JoinedCustomerOrdersView::get)
.invokeAsync(customerId);
}

@Get("/nested-by-customer/{customerId}")
public CompletionStage<CustomerOrders> nestedByCustomer(String customerId) {
return componentClient.forView()
.method(NestedCustomerOrdersView::get)
.invokeAsync(customerId);
}

@Get("/structured-by-customer/{customerId}")
public CompletionStage<store.order.view.structured.CustomerOrders> structuredByCustomer(String customerId) {
return componentClient.forView()
.method(StructuredCustomerOrdersView::get)
.invokeAsync(customerId);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only part that doesn't read so well to me, with importing the different CustomerOrders in different ways — directly, nested class, full package. Could be be better to prefix each of those classes instead:

public CompletionStage<JoinedCustomerOrders> joinedByCustomer(String customerId)

public CompletionStage<NestedCustomerOrders> nestedByCustomer(String customerId)

public CompletionStage<StructuredCustomerOrders> structuredByCustomer(String customerId)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package store.order.api;
package store.order.application;

public record CreateOrder(String productId, String customerId, int quantity) {
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
package store.order.api;
package store.order.application;

import akka.Done;
import akka.javasdk.annotations.ComponentId;
import akka.javasdk.keyvalueentity.KeyValueEntity;
import store.order.domain.Order;

import java.time.Instant;

import static akka.Done.done;

@ComponentId("order")
public class OrderEntity extends KeyValueEntity<Order> {

public Effect<Order> get() {
return effects().reply(currentState());
}

public Effect<String> create(CreateOrder createOrder) {
public Effect<Done> create(CreateOrder createOrder) {
Order order =
new Order(
commandContext().entityId(),
createOrder.productId(),
createOrder.customerId(),
createOrder.quantity(),
Instant.now().toEpochMilli());
return effects().updateState(order).thenReply("OK");
return effects().updateState(order).thenReply(done());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package store.view.joined;
package store.order.view.joined;

import store.customer.domain.Address;
import store.product.domain.Money;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package store.view.joined;
package store.order.view.joined;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've decided to keep a separate "view" package, because that's what we want to highlight in this sample.

import akka.javasdk.annotations.Query;
import akka.javasdk.annotations.Consume;
import akka.javasdk.annotations.Table;
import akka.javasdk.annotations.ComponentId;
import akka.javasdk.view.View;
import akka.javasdk.view.TableUpdater;
import store.customer.api.CustomerEntity;
import store.customer.application.CustomerEntity;
import store.customer.domain.CustomerEvent;
import store.order.api.OrderEntity;
import store.order.application.OrderEntity;
import store.order.domain.Order;
import store.product.api.ProductEntity;
import store.product.application.ProductEntity;
import store.product.domain.ProductEvent;
import store.view.model.Customer;
import store.view.model.Product;
import store.order.view.model.Customer;
import store.order.view.model.Product;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package store.view.model;
package store.order.view.model;

import store.customer.domain.Address;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package store.view.model;
package store.order.view.model;

import store.product.domain.Money;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package store.view.nested;
package store.order.view.nested;

import store.product.domain.Money;

Expand Down
Loading
Loading