Skip to content
This repository has been archived by the owner on Apr 30, 2019. It is now read-only.

Created separate recipe for cassandra write and postgres read side #66

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-J-Xms512M
-J-Xmx4096M
-J-Xss2M
-J-XX:MaxMetaspaceSize=1024M
61 changes: 61 additions & 0 deletions mixed-persistence/mixed-persistence-java-sbt-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Mixed Persistence Service with PostgreSQL
This recipe demonstrates how to create a service in Lagom for Java that uses Cassandra for write-side persistence and JPA for a read-side view.
It was inspired by the
[Mixed Persistence Java SBT](https://github.com/marvinmarnold/lagom-recipes/tree/master/mixed-persistence/mixed-persistence-java-sbt) recipe.
It has been modified to use a PostgreSQL read-side instead of H2.

## Implementation details

The key changes are in [`application.conf`](hello-impl/src/main/resources/application.conf) and [`HelloModule`](hello-impl/src/main/java/com/lightbend/lagom/recipes/mixedpersistence/hello/impl/HelloModule.java).

Specifcally, `JdbcPersistenceModule` is explicitly disabled:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Specifcally, `JdbcPersistenceModule` is explicitly disabled:
Specifically, `JdbcPersistenceModule` is explicitly disabled:


```
play.modules.disabled += com.lightbend.lagom.javadsl.persistence.jdbc.JdbcPersistenceModule
```

Then, an explicit binding for the JDBC offset store is added back:

```java
bind(SlickOffsetStore.class).to(JavadslJdbcOffsetStore.class);
```

### Supporting PostgreSQL instead of H2
1. Add `lagom.persistence.jdbc.create-tables.auto = false` to application.conf
2. Create a PostgreSQL db (note this example runs on the host at post 5431): `docker run -p 5431:5432 --name psql1 -e POSTGRES_PASSWORD=somethingsknown -d postgres
3. Manually create the offset table:
```
CREATE TABLE read_side_offsets (
read_side_id VARCHAR(255), tag VARCHAR(255),
sequence_offset bytea, time_uuid_offset char(36),
PRIMARY KEY (read_side_id, tag)
)
```

## Testing the recipe

You can test this recipe using 2 separate terminals.

On one terminal start the service:

```
sbt runAll
```

On a separate terminal, use `curl` to trigger some events in `HelloService`:

```
curl -H "Content-Type: application/json" -X POST -d '{"message": "Hi"}' http://localhost:9000/api/hello/Alice
curl -H "Content-Type: application/json" -X POST -d '{"message": "Good day"}' http://localhost:9000/api/hello/Bob
curl -H "Content-Type: application/json" -X POST -d '{"message": "Hi"}' http://localhost:9000/api/hello/Carol
curl -H "Content-Type: application/json" -X POST -d '{"message": "Howdy"}' http://localhost:9000/api/hello/David
```

After a few seconds, use `curl` to retrieve a list of all of the stored greetings:

```
curl http://localhost:9000/api/greetings
[{"id":"Alice","message":"Hi"},{"id":"Bob","message":"Good day"},{"id":"Carol","message":"Hi"},{"id":"David","message":"Howdy"}]
```

This is eventually consistent, so it might take a few tries before you see all of the results.
47 changes: 47 additions & 0 deletions mixed-persistence/mixed-persistence-java-sbt-postgres/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import scala.concurrent.duration._

organization in ThisBuild := "com.lightbend.lagom.recipes"
version in ThisBuild := "1.0-SNAPSHOT"

// the Scala version that will be used for cross-compiled libraries
scalaVersion in ThisBuild := "2.12.4"

lazy val `hello` = (project in file("."))
.aggregate(`hello-api`, `hello-impl`)

lazy val `hello-api` = (project in file("hello-api"))
.settings(common: _*)
.settings(
libraryDependencies ++= Seq(
lagomJavadslApi,
lombok
)
)

val lombok = "org.projectlombok" % "lombok" % "1.16.10"
val postgresjdbc = "org.postgresql" % "postgresql" % "42.2.5"
val hibernate = "org.hibernate" % "hibernate-core" % "5.2.12.Final"

lazy val `hello-impl` = (project in file("hello-impl"))
.enablePlugins(LagomJava)
.settings(common: _*)
.settings(
libraryDependencies ++= Seq(
lagomJavadslPersistenceCassandra,
lagomJavadslPersistenceJpa,
lagomJavadslTestKit,
lombok,
postgresjdbc,
hibernate
)
)
.settings(lagomForkedTestSettings: _*)
.dependsOn(`hello-api`)


def common = Seq(
javacOptions in compile += "-parameters"
)

lagomKafkaEnabled in ThisBuild := false
lagomCassandraMaxBootWaitingTime in ThisBuild := 60.seconds
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.api;

import lombok.NonNull;
import lombok.Value;

@Value
public final class GreetingMessage {
@NonNull String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.api;

import akka.Done;
import akka.NotUsed;
import com.lightbend.lagom.javadsl.api.Descriptor;
import com.lightbend.lagom.javadsl.api.Service;
import com.lightbend.lagom.javadsl.api.ServiceCall;
import org.pcollections.PSequence;

import static com.lightbend.lagom.javadsl.api.Service.named;
import static com.lightbend.lagom.javadsl.api.Service.pathCall;

/**
* The hello service interface.
* <p>
* This describes everything that Lagom needs to know about how to serve and
* consume the HelloService.
*/
public interface HelloService extends Service {

/**
* Example: curl http://localhost:9000/api/hello/Alice
*/
ServiceCall<NotUsed, String> hello(String id);

/**
* Examples:
* curl -H "Content-Type: application/json" -X POST -d '{"message": "Hi"}' http://localhost:9000/api/hello/Alice
* curl -H "Content-Type: application/json" -X POST -d '{"message": "Good day"}' http://localhost:9000/api/hello/Bob
* curl -H "Content-Type: application/json" -X POST -d '{"message": "Hi"}' http://localhost:9000/api/hello/Carol
* curl -H "Content-Type: application/json" -X POST -d '{"message": "Howdy"}' http://localhost:9000/api/hello/David
*/
ServiceCall<GreetingMessage, Done> useGreeting(String id);

/**
* Example: curl http://localhost:9000/api/greetings
* Response (pretty-printed):
* <pre>
* [
* {
* "id": "Alice",
* "message": "Hi"
* },
* {
* "id": "Bob",
* "message": "Good day"
* },
* {
* "id": "Carol",
* "message": "Hi"
* },
* {
* "id": "David",
* "message": "Howdy"
* }
* ]
* </pre>
*/
ServiceCall<NotUsed, PSequence<UserGreeting>> allGreetings();

@Override
default Descriptor descriptor() {
return named("hello").withCalls(
pathCall("/api/hello/:id", this::hello),
pathCall("/api/hello/:id", this::useGreeting),
pathCall("/api/greetings", this::allGreetings)
).withAutoAcl(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.api;

import lombok.NonNull;
import lombok.Value;

@Value
public class UserGreeting {
@NonNull String id;
@NonNull String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.impl;

import com.google.inject.AbstractModule;
import com.lightbend.lagom.internal.javadsl.persistence.jdbc.JavadslJdbcOffsetStore;
import com.lightbend.lagom.internal.javadsl.persistence.jdbc.JdbcReadSideImpl;
import com.lightbend.lagom.internal.javadsl.persistence.jdbc.JdbcSessionImpl;
import com.lightbend.lagom.internal.javadsl.persistence.jdbc.SlickProvider;
import com.lightbend.lagom.internal.persistence.jdbc.SlickOffsetStore;
import com.lightbend.lagom.javadsl.persistence.jdbc.JdbcReadSide;
import com.lightbend.lagom.javadsl.persistence.jdbc.JdbcSession;
import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport;
import com.lightbend.lagom.recipes.mixedpersistence.hello.api.HelloService;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.readside.Greetings;
import com.lightbend.lagom.javadsl.persistence.jdbc.GuiceSlickProvider;
/**
* The module that binds the HelloService so that it can be served.
*/
public class HelloModule extends AbstractModule implements ServiceGuiceSupport {
@Override
protected void configure() {
bindService(HelloService.class, HelloServiceImpl.class);
bind(Greetings.class).asEagerSingleton();

// JdbcPersistenceModule is disabled in application.conf to avoid conflicts with CassandraPersistenceModule.
// We need to explicitly re-add the SlickOffsetStore binding that is required by the JpaPersistenceModule.
bind(SlickProvider.class).toProvider(GuiceSlickProvider.class);
bind(SlickOffsetStore.class).to(JavadslJdbcOffsetStore.class);
// To use JdbcReadSide instead of JpaReadSide, uncomment these lines:
// bind(JdbcReadSide.class).to(JdbcReadSideImpl.class);
// bind(JdbcSession.class).to(JdbcSessionImpl.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.impl;

import akka.Done;
import akka.NotUsed;
import com.lightbend.lagom.javadsl.api.ServiceCall;
import com.lightbend.lagom.javadsl.persistence.PersistentEntityRef;
import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry;
import com.lightbend.lagom.recipes.mixedpersistence.hello.api.GreetingMessage;
import com.lightbend.lagom.recipes.mixedpersistence.hello.api.HelloService;
import com.lightbend.lagom.recipes.mixedpersistence.hello.api.UserGreeting;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.entity.HelloCommand;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.entity.HelloCommand.Hello;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.entity.HelloCommand.UseGreetingMessage;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.entity.HelloEntity;
import com.lightbend.lagom.recipes.mixedpersistence.hello.impl.readside.Greetings;
import org.pcollections.PSequence;

import javax.inject.Inject;
import java.util.concurrent.CompletionStage;

/**
* Implementation of the HelloService.
*/
public class HelloServiceImpl implements HelloService {
private final PersistentEntityRegistry persistentEntityRegistry;
private final Greetings greetings;

@Inject
public HelloServiceImpl(PersistentEntityRegistry persistentEntityRegistry, Greetings greetings) {
this.persistentEntityRegistry = persistentEntityRegistry;
this.greetings = greetings;
persistentEntityRegistry.register(HelloEntity.class);
}

@Override
public ServiceCall<NotUsed, String> hello(String id) {
return request -> {
// Look up the hello world entity for the given ID.
PersistentEntityRef<HelloCommand> ref = persistentEntityRegistry.refFor(HelloEntity.class, id);
// Ask the entity the Hello command.
return ref.ask(new Hello(id));
};
}

@Override
public ServiceCall<GreetingMessage, Done> useGreeting(String id) {
return request -> {
// Look up the hello world entity for the given ID.
PersistentEntityRef<HelloCommand> ref = persistentEntityRegistry.refFor(HelloEntity.class, id);
// Tell the entity to use the greeting message specified.
return ref.ask(new UseGreetingMessage(request.getMessage()));
};

}

@Override
public ServiceCall<NotUsed, PSequence<UserGreeting>> allGreetings() {
return request -> null;
// return request -> CompletedFuture.oTreePVector.empty();
// return request -> greetings.all();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.lightbend.lagom.recipes.mixedpersistence.hello.impl.entity;

import akka.Done;
import com.lightbend.lagom.javadsl.persistence.PersistentEntity;
import com.lightbend.lagom.serialization.CompressedJsonable;
import com.lightbend.lagom.serialization.Jsonable;
import lombok.NonNull;
import lombok.Value;

/**
* This interface defines all the commands that the HelloEntity supports.
* <p>
* By convention, the commands should be inner classes of the interface, which
* makes it simple to get a complete picture of what commands an entity
* supports.
*/
public interface HelloCommand extends Jsonable {

/**
* A command to switch the greeting message.
* <p>
* It has a reply type of {@link akka.Done}, which is sent back to the caller
* when all the events emitted by this command are successfully persisted.
*/
@Value
final class UseGreetingMessage implements HelloCommand, CompressedJsonable, PersistentEntity.ReplyType<Done> {
@NonNull String message;
}

/**
* A command to say hello to someone using the current greeting message.
* <p>
* The reply type is String, and will contain the message to say to that
* person.
*/
@Value
final class Hello implements HelloCommand, PersistentEntity.ReplyType<String> {
@NonNull String name;
}

}
Loading