├── .gitignore ├── README.md ├── build-and-test-all.sh ├── build.gradle ├── common ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ ├── common │ ├── aws │ │ ├── AbstractAWSTodoHandler.java │ │ ├── AbstractHttpLambda.java │ │ ├── ApiGatewayRequest.java │ │ ├── ApiGatewayResponse.java │ │ ├── Identity.java │ │ └── RequestContext.java │ ├── event │ │ ├── TodoCreatedEvent.java │ │ ├── TodoDeletedEvent.java │ │ ├── TodoDeletionRequestedEvent.java │ │ ├── TodoEvent.java │ │ └── TodoUpdatedEvent.java │ └── model │ │ └── ResourceWithUrl.java │ └── model │ ├── Todo.java │ └── TodoInfo.java ├── e2etest ├── build.gradle └── src │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── e2etests │ ├── E2ETestConfiguration.java │ ├── EndToEndTest.java │ └── db │ └── TodoDAOTest.java ├── eventhandler-common ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── eventhandler │ └── common │ ├── EventRecord.java │ └── EventsRequest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package.json ├── serverless.yml ├── settings.gradle ├── test-utils ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ ├── AbstractTodoRestAPITest.java │ └── testutil │ ├── BasicWebTestConfiguration.java │ ├── RestTemplateErrorHandler.java │ ├── RestUtil.java │ └── TestUtil.java ├── todo-command-side ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── todoservice │ └── backend │ ├── TodoBackendConfiguration.java │ ├── command │ ├── CreateTodoCommand.java │ ├── DeleteAllTodoCommand.java │ ├── DeleteTodoCommand.java │ ├── DeleteTodosCommand.java │ ├── TodoCommand.java │ └── UpdateTodoCommand.java │ └── domain │ ├── TodoAggregate.java │ ├── TodoBulkDeleteAggregate.java │ └── TodoService.java ├── todo-commandside-lambda ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── commandside │ ├── CommandsideTodoHandler.java │ ├── CommandsideTodoHandlerConfiguration.java │ └── CommandsideTodoLambda.java ├── todo-dynamodb ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── db │ ├── DatabaseTodoConfiguration.java │ ├── Todo.java │ └── TodoDAO.java ├── todo-find-lambda ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── todolist │ └── findlambda │ ├── FindTodoHandler.java │ ├── FindTodoHandlerConfiguration.java │ └── FindTodoLambda.java └── todo-queryside-lambda ├── build.gradle └── src └── main └── java └── net └── chrisrichardson └── eventstore └── examples └── todolist └── queryside ├── QuerysideTodoHandler.java ├── QuerysideTodoHandlerConfiguration.java └── QuerysideTodoLambda.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | *.idea/ 4 | *.iml 5 | *.log 6 | 7 | *.serverless/ 8 | *.serverless_plugins/ 9 | node_modules 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Java AWS Lambda function for Eventuate 2 | 3 | This project is the AWS Lambda/[Serverless framework](http://serverless.com/)-based version of the [Eventuate Todo application](https://github.com/eventuate-examples/eventuate-examples-java-spring-todo-list). 4 | The Todo List application lets users maintain a todo list. 5 | This example illustrates how you can use develop an application with a [microservices architecture](http://microservices.io/patterns/microservices.html) that uses the following: 6 | 7 | * The [Eventuate™ Platform](http://eventuate.io) for [Event Sourcing](http://microservices.io/patterns/data/event-sourcing.html) and [Command Query Responsibility Segregation (CQRS)](http://microservices.io/patterns/data/cqrs.html). 8 | * [AWS Lambda](https://aws.amazon.com/lambda/) 9 | * [Serverless framework](https://serverless.com/) 10 | * The [Eventuate AWS Gateway Plugin](https://github.com/eventuate-clients/eventuate-aws-gateway-serverless-plugin), which enables a serverless lambda to subscribe to Eventuate events. 11 | 12 | # Building the application 13 | 14 | To build the app: 15 | 16 | ``` 17 | ./gradlew build 18 | ``` 19 | 20 | # Installing the plugin 21 | 22 | To install the [Eventuate AWS Gateway plugin for Serverless](https://github.com/eventuate-clients/eventuate-aws-gateway-serverless-plugin): 23 | 24 | ``` 25 | npm install 26 | ``` 27 | or if you are using Vagrant 28 | ``` 29 | npm install --no-bin-links 30 | ``` 31 | 32 | When the todo-queryside-lambda is deployed, this plugin registers it with Eventuate. 33 | When the todo-queryside-lambda undeployed, the plugin unregisters it. 34 | See the `serverless.yml` for the details of how the events of interest are specified. 35 | 36 | # Deploying the lambdas 37 | 38 | Set the Eventuate environment variables: `EVENTUATE_API_KEY_ID` and `EVENTUATE_API_KEY_SECRET`. 39 | 40 | To deploy the lambdas: 41 | 42 | ``` 43 | serverless deploy 44 | ``` 45 | 46 | 47 | # Accessing the application 48 | 49 | First, set an environment variable to the root URL. 50 | Serverless displays this when you lambdas are deployed. 51 | Alternatively, run this command: 52 | 53 | ``` 54 | export BASE_URL=$(serverless info -v | grep ServiceEndpoint | cut -d' ' -f2) 55 | ``` 56 | 57 | To create a todo: 58 | 59 | ``` 60 | curl -d '{"title" : "Say Hello"}' $BASE_URL/todos 61 | ``` 62 | 63 | Make a note of the id of the created todo, eg. 64 | 65 | ``` 66 | export ID=xxxx-yyyy 67 | ``` 68 | 69 | 70 | To get the Todo: 71 | 72 | ``` 73 | curl $BASE_URL/todos/$ID 74 | 75 | ``` 76 | 77 | To get all todos: 78 | 79 | ``` 80 | curl $BASE_URL/todos 81 | 82 | ``` 83 | 84 | To update a Todo: 85 | 86 | ``` 87 | curl -X PUT -d '{"title" : "Say Hello Again"}' $BASE_URL/todos/$ID 88 | ``` 89 | 90 | To delete a Todo: 91 | 92 | ``` 93 | curl -X DELETE $BASE_URL/todos/$ID 94 | ``` 95 | 96 | 97 | # Got questions? 98 | 99 | Don't hesitate to create an issue or see 100 | 101 | * [Website](http://eventuate.io) 102 | * [Mailing list](https://groups.google.com/d/forum/eventuate-users) 103 | * [Slack](https://eventuate-users.slack.com). [Get invite](https://eventuateusersslack.herokuapp.com/) 104 | * [Contact us](http://eventuate.io/contact.html). 105 | -------------------------------------------------------------------------------- /build-and-test-all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | if [ -z "$EVENTUATE_API_KEY_ID" -o -z "$EVENTUATE_API_KEY_SECRET" ] ; then 6 | echo You must set EVENTUATE_API_KEY_ID and EVENTUATE_API_KEY_SECRET 7 | exit -1 8 | fi 9 | 10 | if [ -z "$AWS_ACCESS_KEY_ID" ] ; then 11 | echo You must set AWS_ACCESS_KEY_ID to be able to run serverless deploy 12 | exit -1 13 | fi 14 | if [ -z "$AWS_SECRET_ACCESS_KEY" ] ; then 15 | echo You must set AWS_SECRET_ACCESS_KEY to be able to run serverless deploy 16 | exit -1 17 | fi 18 | if [ -z "$AWS_REGION" ] ; then 19 | echo You must set AWS_REGION to be able to run serverless deploy 20 | exit -1 21 | fi 22 | 23 | ./gradlew clean test buildZip -x :e2etest:test 24 | 25 | serverless -r $AWS_REGION deploy 26 | 27 | export AWS_GATEWAY_API_INVOKE_URL=$(serverless -r $AWS_REGION info -v | grep ServiceEndpoint | cut -d' ' -f2) 28 | echo set export AWS_GATEWAY_API_INVOKE_URL=$AWS_GATEWAY_API_INVOKE_URL 29 | 30 | ./gradlew -P ignoreE2EFailures=false :e2etest:cleanTest :e2etest:test 31 | 32 | serverless -r $AWS_REGION remove 33 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") 7 | } 8 | } 9 | 10 | task wrapper(type: Wrapper) { 11 | gradleVersion = '2.0' 12 | } 13 | 14 | subprojects { 15 | apply plugin: 'java' 16 | sourceCompatibility = 1.8 17 | targetCompatibility = 1.8 18 | 19 | repositories { 20 | mavenCentral() 21 | jcenter() 22 | eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } } 23 | } 24 | } -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion" 3 | compile "com.amazonaws:aws-lambda-java-core:1.1.0" 4 | testCompile 'junit:junit:4.11' 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/AbstractAWSTodoHandler.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.RequestHandler; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | public abstract class AbstractAWSTodoHandler implements RequestHandler { 9 | 10 | protected Logger logger = LoggerFactory.getLogger(this.getClass()); 11 | 12 | @Override 13 | public ApiGatewayResponse handleRequest(I input, Context context) { 14 | logger.debug("Got request: {}", input); 15 | 16 | return handleAWSRequest(input, context); 17 | } 18 | 19 | protected abstract ApiGatewayResponse handleAWSRequest(I request, Context context); 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/AbstractHttpLambda.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import com.amazonaws.services.lambda.runtime.RequestHandler; 5 | import org.springframework.context.ApplicationContext; 6 | 7 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.build500ErrorResponse; 8 | 9 | public abstract class AbstractHttpLambda implements RequestHandler { 10 | 11 | protected final ApplicationContext applicationContext; 12 | 13 | public AbstractHttpLambda(ApplicationContext applicationContext) { 14 | this.applicationContext = applicationContext; 15 | } 16 | 17 | public ApiGatewayResponse handleRequest(I request, Context context) { 18 | try { 19 | return applicationContext.getBean(AbstractAWSTodoHandler.class).handleRequest(request, context); 20 | } catch (Exception e) { 21 | return build500ErrorResponse(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/ApiGatewayRequest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | 5 | import java.util.Map; 6 | 7 | public class ApiGatewayRequest { 8 | 9 | private String resource; 10 | private String path; 11 | private String httpMethod; 12 | private Map headers; 13 | private Map queryStringParameters; 14 | private Map pathParameters; 15 | private Map stageVariables; 16 | private RequestContext requestContext; 17 | private String body; 18 | private boolean isBase64Encoded; 19 | 20 | public String getResource() { 21 | return resource; 22 | } 23 | 24 | public void setResource(String resource) { 25 | this.resource = resource; 26 | } 27 | 28 | public String getPath() { 29 | return path; 30 | } 31 | 32 | public void setPath(String path) { 33 | this.path = path; 34 | } 35 | 36 | public String getHttpMethod() { 37 | return httpMethod; 38 | } 39 | 40 | public void setHttpMethod(String httpMethod) { 41 | this.httpMethod = httpMethod; 42 | } 43 | 44 | public Map getHeaders() { 45 | return headers; 46 | } 47 | 48 | public void setHeaders(Map headers) { 49 | this.headers = headers; 50 | } 51 | 52 | public Map getQueryStringParameters() { 53 | return queryStringParameters; 54 | } 55 | 56 | public void setQueryStringParameters(Map queryStringParameters) { 57 | this.queryStringParameters = queryStringParameters; 58 | } 59 | 60 | public Map getPathParameters() { 61 | return pathParameters; 62 | } 63 | 64 | public void setPathParameters(Map pathParameters) { 65 | this.pathParameters = pathParameters; 66 | } 67 | 68 | public Map getStageVariables() { 69 | return stageVariables; 70 | } 71 | 72 | public void setStageVariables(Map stageVariables) { 73 | this.stageVariables = stageVariables; 74 | } 75 | 76 | public RequestContext getRequestContext() { 77 | return requestContext; 78 | } 79 | 80 | public void setRequestContext(RequestContext requestContext) { 81 | this.requestContext = requestContext; 82 | } 83 | 84 | public String getBody() { 85 | return body; 86 | } 87 | 88 | public void setBody(String body) { 89 | this.body = body; 90 | } 91 | 92 | public boolean isBase64Encoded() { 93 | return isBase64Encoded; 94 | } 95 | 96 | public void setBase64Encoded(boolean base64Encoded) { 97 | isBase64Encoded = base64Encoded; 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return ToStringBuilder.reflectionToString(this); 103 | } 104 | } -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/ApiGatewayResponse.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | import io.eventuate.javaclient.commonimpl.JSonMapper; 4 | 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.Base64; 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class ApiGatewayResponse { 12 | private final int statusCode; 13 | private final String body; 14 | private final Map headers; 15 | private final boolean isBase64Encoded; 16 | 17 | public ApiGatewayResponse(int statusCode, String body, Map headers, boolean isBase64Encoded) { 18 | this.statusCode = statusCode; 19 | this.body = body; 20 | this.headers = headers; 21 | this.isBase64Encoded = isBase64Encoded; 22 | } 23 | 24 | public int getStatusCode() { 25 | return statusCode; 26 | } 27 | 28 | public String getBody() { 29 | return body; 30 | } 31 | 32 | public Map getHeaders() { 33 | return headers; 34 | } 35 | 36 | // API Gateway expects the property to be called "isBase64Encoded" => isIs 37 | public boolean isIsBase64Encoded() { 38 | return isBase64Encoded; 39 | } 40 | 41 | public static Builder builder() { 42 | return new Builder(); 43 | } 44 | 45 | public static class Builder { 46 | private int statusCode = 200; 47 | private Map headers = Collections.emptyMap(); 48 | private String rawBody; 49 | private Object objectBody; 50 | private byte[] binaryBody; 51 | private boolean base64Encoded; 52 | 53 | public Builder setStatusCode(int statusCode) { 54 | this.statusCode = statusCode; 55 | return this; 56 | } 57 | 58 | public Builder setHeaders(Map headers) { 59 | this.headers = headers; 60 | return this; 61 | } 62 | 63 | public Builder setRawBody(String rawBody) { 64 | this.rawBody = rawBody; 65 | return this; 66 | } 67 | 68 | public Builder setObjectBody(Object objectBody) { 69 | this.objectBody = objectBody; 70 | return this; 71 | } 72 | 73 | public Builder setBinaryBody(byte[] binaryBody) { 74 | this.binaryBody = binaryBody; 75 | setBase64Encoded(true); 76 | return this; 77 | } 78 | 79 | public Builder setBase64Encoded(boolean base64Encoded) { 80 | this.base64Encoded = base64Encoded; 81 | return this; 82 | } 83 | 84 | public ApiGatewayResponse build() { 85 | String body = null; 86 | if (rawBody != null) { 87 | body = rawBody; 88 | } else if (objectBody != null) { 89 | body = JSonMapper.toJson(objectBody); 90 | } else if (binaryBody != null) { 91 | body = new String(Base64.getEncoder().encode(binaryBody), StandardCharsets.UTF_8); 92 | } 93 | return new ApiGatewayResponse(statusCode, body, headers, base64Encoded); 94 | } 95 | } 96 | 97 | public static ApiGatewayResponse build500ErrorResponse() { 98 | return buildErrorResponse(500, "Internal server error"); 99 | } 100 | 101 | public static ApiGatewayResponse build405ErrorResponse() { 102 | return buildErrorResponse(405, "HTTP method not allowed"); 103 | } 104 | 105 | public static ApiGatewayResponse build404ErrorResponse() { 106 | return buildErrorResponse(404, "No entity found"); 107 | } 108 | 109 | private static ApiGatewayResponse buildErrorResponse(int status, String message) { 110 | return ApiGatewayResponse.builder() 111 | .setStatusCode(status) 112 | .setObjectBody(message) 113 | .setHeaders(applicationJsonHeaders()) 114 | .build(); 115 | } 116 | 117 | public static Map applicationJsonHeaders() { 118 | Map headers = new HashMap<>(); 119 | headers.put("Content-Type", "application/json"); 120 | return headers; 121 | } 122 | } -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/Identity.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | public class Identity { 4 | 5 | private String cognitoIdentityPoolId; 6 | private String accountId; 7 | private String cognitoIdentityId; 8 | private String caller; 9 | private String apiKey; 10 | private String sourceIp; 11 | private String cognitoAuthenticationType; 12 | private String cognitoAuthenticationProvider; 13 | private String userArn; 14 | private String userAgent; 15 | private String user; 16 | 17 | public String getCognitoIdentityPoolId() { 18 | return cognitoIdentityPoolId; 19 | } 20 | 21 | public void setCognitoIdentityPoolId(String cognitoIdentityPoolId) { 22 | this.cognitoIdentityPoolId = cognitoIdentityPoolId; 23 | } 24 | 25 | public String getAccountId() { 26 | return accountId; 27 | } 28 | 29 | public void setAccountId(String accountId) { 30 | this.accountId = accountId; 31 | } 32 | 33 | public String getCognitoIdentityId() { 34 | return cognitoIdentityId; 35 | } 36 | 37 | public void setCognitoIdentityId(String cognitoIdentityId) { 38 | this.cognitoIdentityId = cognitoIdentityId; 39 | } 40 | 41 | public String getCaller() { 42 | return caller; 43 | } 44 | 45 | public void setCaller(String caller) { 46 | this.caller = caller; 47 | } 48 | 49 | public String getApiKey() { 50 | return apiKey; 51 | } 52 | 53 | public void setApiKey(String apiKey) { 54 | this.apiKey = apiKey; 55 | } 56 | 57 | public String getSourceIp() { 58 | return sourceIp; 59 | } 60 | 61 | public void setSourceIp(String sourceIp) { 62 | this.sourceIp = sourceIp; 63 | } 64 | 65 | public String getCognitoAuthenticationType() { 66 | return cognitoAuthenticationType; 67 | } 68 | 69 | public void setCognitoAuthenticationType(String cognitoAuthenticationType) { 70 | this.cognitoAuthenticationType = cognitoAuthenticationType; 71 | } 72 | 73 | public String getCognitoAuthenticationProvider() { 74 | return cognitoAuthenticationProvider; 75 | } 76 | 77 | public void setCognitoAuthenticationProvider(String cognitoAuthenticationProvider) { 78 | this.cognitoAuthenticationProvider = cognitoAuthenticationProvider; 79 | } 80 | 81 | public String getUserArn() { 82 | return userArn; 83 | } 84 | 85 | public void setUserArn(String userArn) { 86 | this.userArn = userArn; 87 | } 88 | 89 | public String getUserAgent() { 90 | return userAgent; 91 | } 92 | 93 | public void setUserAgent(String userAgent) { 94 | this.userAgent = userAgent; 95 | } 96 | 97 | public String getUser() { 98 | return user; 99 | } 100 | 101 | public void setUser(String user) { 102 | this.user = user; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/aws/RequestContext.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.aws; 2 | 3 | public class RequestContext { 4 | 5 | private String accountId; 6 | private String resourceId; 7 | private String stage; 8 | private String requestId; 9 | private Identity identity; 10 | private String resourcePath; 11 | private String httpMethod; 12 | private String apiId; 13 | 14 | public String getAccountId() { 15 | return accountId; 16 | } 17 | 18 | public void setAccountId(String accountId) { 19 | this.accountId = accountId; 20 | } 21 | 22 | public String getResourceId() { 23 | return resourceId; 24 | } 25 | 26 | public void setResourceId(String resourceId) { 27 | this.resourceId = resourceId; 28 | } 29 | 30 | public String getStage() { 31 | return stage; 32 | } 33 | 34 | public void setStage(String stage) { 35 | this.stage = stage; 36 | } 37 | 38 | public String getRequestId() { 39 | return requestId; 40 | } 41 | 42 | public void setRequestId(String requestId) { 43 | this.requestId = requestId; 44 | } 45 | 46 | public Identity getIdentity() { 47 | return identity; 48 | } 49 | 50 | public void setIdentity(Identity identity) { 51 | this.identity = identity; 52 | } 53 | 54 | public String getResourcePath() { 55 | return resourcePath; 56 | } 57 | 58 | public void setResourcePath(String resourcePath) { 59 | this.resourcePath = resourcePath; 60 | } 61 | 62 | public String getHttpMethod() { 63 | return httpMethod; 64 | } 65 | 66 | public void setHttpMethod(String httpMethod) { 67 | this.httpMethod = httpMethod; 68 | } 69 | 70 | public String getApiId() { 71 | return apiId; 72 | } 73 | 74 | public void setApiId(String apiId) { 75 | this.apiId = apiId; 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/event/TodoCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.event; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 4 | 5 | public class TodoCreatedEvent implements TodoEvent { 6 | 7 | TodoInfo todo; 8 | 9 | private TodoCreatedEvent() { 10 | } 11 | 12 | public TodoCreatedEvent(TodoInfo todo) { 13 | this.todo = todo; 14 | } 15 | 16 | public TodoInfo getTodo() { 17 | return todo; 18 | } 19 | 20 | public void setTodo(TodoInfo todo) { 21 | this.todo = todo; 22 | } 23 | } -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/event/TodoDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.event; 2 | 3 | 4 | public class TodoDeletedEvent implements TodoEvent { 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/event/TodoDeletionRequestedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.event; 2 | 3 | 4 | import io.eventuate.Event; 5 | 6 | public class TodoDeletionRequestedEvent implements Event { 7 | 8 | private String todoId; 9 | 10 | public TodoDeletionRequestedEvent(String todoId) { 11 | this.todoId = todoId; 12 | } 13 | 14 | public TodoDeletionRequestedEvent() { 15 | 16 | } 17 | 18 | public String getTodoId() { 19 | return todoId; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/event/TodoEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.event; 2 | 3 | 4 | import io.eventuate.Event; 5 | 6 | public interface TodoEvent extends Event { 7 | } 8 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/event/TodoUpdatedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.event; 2 | 3 | 4 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 5 | 6 | public class TodoUpdatedEvent implements TodoEvent { 7 | 8 | private TodoInfo todo; 9 | 10 | private TodoUpdatedEvent() { 11 | } 12 | 13 | public TodoUpdatedEvent(TodoInfo todo) { 14 | this.todo = todo; 15 | } 16 | 17 | public TodoInfo getTodo() { 18 | return todo; 19 | } 20 | 21 | public void setTodo(TodoInfo todo) { 22 | this.todo = todo; 23 | } 24 | } -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/common/model/ResourceWithUrl.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.common.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 4 | 5 | import javax.xml.bind.annotation.XmlAnyElement; 6 | import javax.xml.bind.annotation.XmlRootElement; 7 | 8 | @XmlRootElement 9 | public class ResourceWithUrl { 10 | private String id; 11 | @JsonUnwrapped 12 | private T content; 13 | 14 | private String url; 15 | 16 | public ResourceWithUrl() { 17 | } 18 | 19 | public ResourceWithUrl(T content) { 20 | this.content = content; 21 | } 22 | 23 | public String getId() { 24 | return id; 25 | } 26 | 27 | public void setId(String id) { 28 | this.id = id; 29 | } 30 | 31 | @XmlAnyElement 32 | public T getContent() { 33 | return content; 34 | } 35 | 36 | public void setContent(T content) { 37 | this.content = content; 38 | } 39 | 40 | public String getUrl() { 41 | return url; 42 | } 43 | 44 | public void setUrl(String url) { 45 | this.url = url; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/model/Todo.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.model; 2 | 3 | 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.ToStringBuilder; 6 | 7 | public class Todo { 8 | 9 | private String id; 10 | private String title; 11 | private Boolean completed; 12 | private Integer order; 13 | 14 | public Todo() { 15 | } 16 | 17 | public Todo(String id, TodoInfo todoInfo) { 18 | this.id = id; 19 | this.title = todoInfo.getTitle(); 20 | this.completed = todoInfo.isCompleted(); 21 | this.order = todoInfo.getOrder(); 22 | } 23 | 24 | public Todo(String title) { 25 | this.title = title; 26 | } 27 | 28 | public Todo(String id, String title, Boolean completed, Integer order) { 29 | this.id = id; 30 | this.title = title; 31 | this.completed = completed; 32 | this.order = order; 33 | } 34 | 35 | public String getTitle() { 36 | return title; 37 | } 38 | 39 | public void setTitle(String title) { 40 | this.title = title; 41 | } 42 | 43 | public String getId() { 44 | return id; 45 | } 46 | 47 | public void setId(String id) { 48 | this.id = id; 49 | } 50 | 51 | public boolean isCompleted() { 52 | return nonNull(completed, false); 53 | } 54 | 55 | public void setCompleted(boolean completed) { 56 | this.completed = completed; 57 | } 58 | 59 | public Integer getOrder() { 60 | return nonNull(order, 0); 61 | } 62 | 63 | public void setOrder(Integer order) { 64 | this.order = order; 65 | } 66 | 67 | public Todo merge(Todo newTodo) { 68 | return new Todo(id, 69 | nonNull(newTodo.title, title), 70 | nonNull(newTodo.completed, completed), 71 | nonNull(newTodo.order, order)); 72 | } 73 | 74 | @Override 75 | public boolean equals(Object o) { 76 | return EqualsBuilder.reflectionEquals(this, o); 77 | } 78 | 79 | private T nonNull(T value, T defaultValue) { 80 | return value == null ? defaultValue : value; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return ToStringBuilder.reflectionToString(this); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/model/TodoInfo.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.model; 2 | 3 | 4 | public class TodoInfo { 5 | private String title; 6 | private boolean completed; 7 | private int order; 8 | 9 | public TodoInfo() { 10 | } 11 | 12 | public TodoInfo(String title) { 13 | this.title = title; 14 | } 15 | 16 | public String getTitle() { 17 | return title; 18 | } 19 | 20 | public void setTitle(String title) { 21 | this.title = title; 22 | } 23 | 24 | public boolean isCompleted() { 25 | return completed; 26 | } 27 | 28 | public void setCompleted(boolean completed) { 29 | this.completed = completed; 30 | } 31 | 32 | public int getOrder() { 33 | return order; 34 | } 35 | 36 | public void setOrder(int order) { 37 | this.order = order; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /e2etest/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | dependencies { 4 | compile project(":todo-dynamodb") 5 | testCompile "com.amazonaws:aws-java-sdk-dynamodb:$awsJavaSdkVersion" 6 | testCompile "org.apache.httpcomponents:httpclient:4.5" 7 | testCompile project(":test-utils") 8 | testCompile "junit:junit:4.11" 9 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | } 11 | 12 | test { 13 | ignoreFailures(!project.hasProperty("ignoreE2EFailures") || ignoreE2EFailures.toBoolean()) 14 | 15 | beforeSuite { x -> 16 | if (x.parent == null) { 17 | logger.warn('These tests will fail unless AWS_GATEWAY_API_INVOKE_URL is set, the Todo lambdas are running and AGI Gateway endpoints are deployed') 18 | logger.warn("But don't worry the build is configured to ignore the failure") 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /e2etest/src/test/java/net/chrisrichardson/eventstore/examples/todolist/e2etests/E2ETestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.e2etests; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.testutil.BasicWebTestConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @EnableAutoConfiguration 9 | public class E2ETestConfiguration extends BasicWebTestConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /e2etest/src/test/java/net/chrisrichardson/eventstore/examples/todolist/e2etests/EndToEndTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.e2etests; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.AbstractTodoRestAPITest; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | 8 | @SpringBootTest(classes = {E2ETestConfiguration.class}) 9 | public class EndToEndTest extends AbstractTodoRestAPITest { 10 | 11 | @Value("#{systemEnvironment['AWS_GATEWAY_API_INVOKE_URL']}") 12 | private String apiGatewayInvokeUrl; 13 | 14 | @Override 15 | protected String getHost() { 16 | return apiGatewayInvokeUrl; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /e2etest/src/test/java/net/chrisrichardson/eventstore/examples/todolist/e2etests/db/TodoDAOTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.e2etests.db; 2 | 3 | 4 | import net.chrisrichardson.eventstore.examples.todolist.db.DatabaseTodoConfiguration; 5 | import net.chrisrichardson.eventstore.examples.todolist.db.Todo; 6 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 7 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | 14 | import java.util.UUID; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @SpringBootTest(properties = { 20 | "eventuate.aws.accessKeyId=${AWS_ACCESS_KEY_ID}", 21 | "eventuate.aws.secretAccessKey=${AWS_SECRET_ACCESS_KEY}", 22 | "eventuate.aws.region=${AWS_REGION}", 23 | }, classes = DatabaseTodoConfiguration.class) 24 | public class TodoDAOTest { 25 | 26 | @Autowired 27 | private TodoDAO todoDAO; 28 | 29 | @Test 30 | public void shouldSaveAndGetTodo() { 31 | Todo todo = generateTodo(); 32 | todoDAO.save(todo); 33 | 34 | Todo savedTodo = todoDAO.findOne(todo.getId()).get(); 35 | assertEquals(todo, savedTodo); 36 | } 37 | 38 | 39 | private Todo generateTodo() { 40 | Todo todo = new Todo(new TodoInfo(generateId())); 41 | todo.setId(generateId()); 42 | return todo; 43 | } 44 | 45 | private String generateId() { 46 | return UUID.randomUUID().toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /eventhandler-common/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile "com.amazonaws:aws-lambda-java-core:1.1.0" 4 | compile "io.eventuate.client.java:eventuate-client-java:$eventuateClientVersion" 5 | compile "org.springframework.boot:spring-boot:$springBootVersion" 6 | } 7 | -------------------------------------------------------------------------------- /eventhandler-common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/eventhandler/common/EventRecord.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.eventhandler.common; 2 | 3 | import org.apache.commons.lang.builder.ToStringBuilder; 4 | 5 | public class EventRecord { 6 | private String entityId; 7 | private String eventId; 8 | private String eventType; 9 | private String eventData; 10 | private String entityType; 11 | private String space; 12 | private String userId; 13 | 14 | public EventRecord() { 15 | } 16 | 17 | public EventRecord(String entityId, String eventId, String eventType, String eventData, String entityType, String space, String userId) { 18 | this.entityId = entityId; 19 | this.eventId = eventId; 20 | this.eventType = eventType; 21 | this.eventData = eventData; 22 | this.entityType = entityType; 23 | this.space = space; 24 | this.userId = userId; 25 | } 26 | 27 | public String getEntityId() { 28 | return entityId; 29 | } 30 | 31 | public String getEventId() { 32 | return eventId; 33 | } 34 | 35 | public String getEventType() { 36 | return eventType; 37 | } 38 | 39 | public String getEventData() { 40 | return eventData; 41 | } 42 | 43 | public String getEntityType() { 44 | return entityType; 45 | } 46 | 47 | public void setEntityType(String entityType) { 48 | this.entityType = entityType; 49 | } 50 | 51 | public String getSpace() { 52 | return space; 53 | } 54 | 55 | public void setSpace(String space) { 56 | this.space = space; 57 | } 58 | 59 | public String getUserId() { 60 | return userId; 61 | } 62 | 63 | public void setUserId(String userId) { 64 | this.userId = userId; 65 | } 66 | 67 | public void setEntityId(String entityId) { 68 | this.entityId = entityId; 69 | } 70 | 71 | public void setEventId(String eventId) { 72 | this.eventId = eventId; 73 | } 74 | 75 | public void setEventType(String eventType) { 76 | this.eventType = eventType; 77 | } 78 | 79 | public void setEventData(String eventData) { 80 | this.eventData = eventData; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return ToStringBuilder.reflectionToString(this); 86 | } 87 | 88 | public boolean isEventType(Class clasz) { 89 | return clasz.getName().equals(eventType); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /eventhandler-common/src/main/java/net/chrisrichardson/eventstore/examples/todolist/eventhandler/common/EventsRequest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.eventhandler.common; 2 | 3 | import java.util.List; 4 | 5 | public class EventsRequest { 6 | private List events; 7 | 8 | public EventsRequest() { 9 | } 10 | 11 | public EventsRequest(List events) { 12 | this.events = events; 13 | } 14 | 15 | public List getEvents() { 16 | return events; 17 | } 18 | 19 | public void setEvents(List events) { 20 | this.events = events; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-XX:MaxPermSize=512m 2 | 3 | eventuateMavenRepoUrl= 4 | 5 | springBootVersion=1.4.3.RELEASE 6 | 7 | eventuateClientVersion=0.14.0.RELEASE 8 | 9 | awsJavaSdkVersion=1.11.87 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-examples/eventuate-examples-java-aws-lambda-todo-list/96a0d2d10464c6cec1277959ca5567886dbba9ba/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 30 05:25:45 PST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip 7 | 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eventuate-examples-java-aws-lambda-todo-list", 3 | "version": "1.0.0", 4 | "description": "AWS Lambda, Serverless framework version of the Eventuate Todo List application", 5 | "main": "index.js", 6 | "dependencies": { 7 | "eventuate-aws-gateway-serverless-plugin": "0.0.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/eventuate-examples/eventuate-examples-java-aws-lambda-todo-list" 16 | }, 17 | "author": "", 18 | "license": "Apache-2.0", 19 | "bugs": { 20 | "url": "https://github.com/eventuate-examples/eventuate-examples-java-aws-lambda-todo-list/issues" 21 | }, 22 | "homepage": "https://github.com/eventuate-examples/eventuate-examples-java-aws-lambda-todo-list" 23 | } 24 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: eventuate-examples-aws-todo-list 2 | 3 | package: 4 | individually: true 5 | 6 | provider: 7 | name: aws 8 | runtime: java8 9 | timeout: 10 10 | stage: dev 11 | environment: 12 | EVENTUATE_API_KEY_ID: ${env:EVENTUATE_API_KEY_ID} 13 | EVENTUATE_API_KEY_SECRET: ${env:EVENTUATE_API_KEY_SECRET} 14 | iamRoleStatements: 15 | - Effect: Allow 16 | Action: 17 | - dynamodb:Query 18 | - dynamodb:Scan 19 | - dynamodb:GetItem 20 | - dynamodb:PutItem 21 | - dynamodb:UpdateItem 22 | - dynamodb:DeleteItem 23 | - dynamodb:BatchWriteItem 24 | Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/todo_records" 25 | 26 | functions: 27 | findTodo: 28 | handler: net.chrisrichardson.eventstore.examples.todolist.findlambda.FindTodoLambda 29 | package: 30 | artifact: todo-find-lambda/build/distributions/todo-find-lambda.zip 31 | events: 32 | - http: 33 | path: /todos/{todoId+} 34 | method: get 35 | cors: true 36 | - http: 37 | path: /todos 38 | method: get 39 | cors: true 40 | 41 | commandsideTodoLambda: 42 | handler: net.chrisrichardson.eventstore.examples.todolist.commandside.CommandsideTodoLambda 43 | package: 44 | artifact: todo-commandside-lambda/build/distributions/todo-commandside-lambda.zip 45 | events: 46 | - http: 47 | path: /todos 48 | method: post 49 | - http: 50 | path: /todos/{todoId+} 51 | method: put 52 | - http: 53 | path: /todos/{todoId+} 54 | method: delete 55 | - http: 56 | path: /todos 57 | method: delete 58 | 59 | querysideTodoLambda: 60 | handler: net.chrisrichardson.eventstore.examples.todolist.queryside.QuerysideTodoLambda 61 | package: 62 | artifact: todo-queryside-lambda/build/distributions/todo-queryside-lambda.zip 63 | events: 64 | - eventuate: 65 | subscriberId: todoSampleLambdaEventHandler 66 | space: default 67 | entitiesAndEventTypes: 68 | net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoAggregate: 69 | - net.chrisrichardson.eventstore.examples.todolist.common.event.TodoCreatedEvent 70 | - net.chrisrichardson.eventstore.examples.todolist.common.event.TodoUpdatedEvent 71 | - net.chrisrichardson.eventstore.examples.todolist.common.event.TodoDeletedEvent 72 | 73 | resources: 74 | Resources: 75 | TodosDynamoDbTable: 76 | Type: 'AWS::DynamoDB::Table' 77 | DeletionPolicy: Retain 78 | Properties: 79 | AttributeDefinitions: 80 | - 81 | AttributeName: id 82 | AttributeType: S 83 | KeySchema: 84 | - 85 | AttributeName: id 86 | KeyType: HASH 87 | ProvisionedThroughput: 88 | ReadCapacityUnits: 1 89 | WriteCapacityUnits: 1 90 | TableName: todo_records 91 | 92 | plugins: 93 | - eventuate-aws-gateway-serverless-plugin -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'eventuate-examples-java-aws-lambda-todo-list' 2 | include 'common' 3 | include 'todo-command-side' 4 | include 'test-utils' 5 | include 'e2etest' 6 | include 'eventhandler-common' 7 | include 'todo-dynamodb' 8 | include 'todo-find-lambda' 9 | include 'todo-commandside-lambda' 10 | include 'todo-queryside-lambda' 11 | 12 | -------------------------------------------------------------------------------- /test-utils/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | dependencies { 4 | compile project(":common") 5 | compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion" 6 | 7 | compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") 8 | compile "junit:junit:4.11" 9 | compile "io.reactivex:rxjava:1.1.5" 10 | compile "org.apache.httpcomponents:httpclient:4.5" 11 | compile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 12 | } -------------------------------------------------------------------------------- /test-utils/src/main/java/net/chrisrichardson/eventstore/examples/todolist/AbstractTodoRestAPITest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist; 2 | 3 | 4 | import net.chrisrichardson.eventstore.examples.todolist.model.Todo; 5 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpEntity; 11 | import org.springframework.http.HttpMethod; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.web.client.RestTemplate; 16 | 17 | import java.util.Arrays; 18 | import java.util.List; 19 | 20 | import static net.chrisrichardson.eventstore.examples.todolist.testutil.TestUtil.awaitNotFoundResponse; 21 | import static net.chrisrichardson.eventstore.examples.todolist.testutil.TestUtil.awaitSuccessfulRequest; 22 | 23 | 24 | @RunWith(SpringJUnit4ClassRunner.class) 25 | public abstract class AbstractTodoRestAPITest { 26 | 27 | private String baseUrl(String path) { 28 | return getHost() + "/" + path; 29 | } 30 | 31 | @Autowired 32 | private RestTemplate restTemplate; 33 | 34 | 35 | private Todo awaitCreationInView(String todoId) { 36 | return awaitSuccessfulRequest(() -> getTodo(todoId)); 37 | } 38 | 39 | private ResponseEntity createTodo(TodoInfo todoToSave) { 40 | ResponseEntity postResponse = restTemplate.postForEntity(baseUrl("todos"), todoToSave, Todo.class); 41 | Assert.assertEquals(HttpStatus.OK, postResponse.getStatusCode()); 42 | return postResponse; 43 | } 44 | 45 | 46 | private ResponseEntity getTodo(String todoId) { 47 | return restTemplate.getForEntity(baseUrl("todos/" + todoId), Todo.class); 48 | } 49 | 50 | private ResponseEntity getTodos() { 51 | return restTemplate.getForEntity(baseUrl("todos"), Todo[].class); 52 | } 53 | 54 | private void assertTodoEquals(Todo expectedTodo, Todo todo) { 55 | Assert.assertEquals(expectedTodo.getTitle(), todo.getTitle()); 56 | Assert.assertEquals(expectedTodo.getOrder(), todo.getOrder()); 57 | Assert.assertEquals(expectedTodo.isCompleted(), todo.isCompleted()); 58 | } 59 | 60 | private void assertTodoContains(Todo expectedTodo, List todoList) { 61 | Assert.assertTrue(todoList.contains(expectedTodo)); 62 | } 63 | 64 | private Todo makeExpectedTodo(String todoId, TodoInfo todo) { 65 | Todo todoWithUrl = new Todo(); 66 | todoWithUrl.setCompleted(todo.isCompleted()); 67 | todoWithUrl.setOrder(todo.getOrder()); 68 | todoWithUrl.setTitle(todo.getTitle()); 69 | todoWithUrl.setId(todoId); 70 | return todoWithUrl; 71 | } 72 | 73 | private ResponseEntity updateTodo(String todoId, TodoInfo put) { 74 | ResponseEntity putResult = restTemplate.exchange(baseUrl("todos/" + todoId), HttpMethod.PUT, new HttpEntity<>(put), 75 | Todo.class); 76 | Assert.assertEquals(HttpStatus.OK, putResult.getStatusCode()); 77 | return putResult; 78 | } 79 | 80 | private Todo createAndWaitForView(TodoInfo todoToSave) { 81 | ResponseEntity postResponse = createTodo(todoToSave); 82 | 83 | String todoId = postResponse.getBody().getId(); 84 | 85 | return awaitCreationInView(todoId); 86 | } 87 | 88 | @Test 89 | public void shouldSetCORSHeaders() { 90 | ResponseEntity responseEntity = restTemplate.exchange(baseUrl("todos"), HttpMethod.OPTIONS, new HttpEntity<>(""), Void.class); 91 | 92 | Assert.assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); 93 | Assert.assertFalse(responseEntity.getHeaders().get("Access-Control-Allow-Origin").isEmpty()); 94 | Assert.assertEquals("*", responseEntity.getHeaders().get("Access-Control-Allow-Origin").get(0)); 95 | Assert.assertFalse(responseEntity.getHeaders().get("Access-Control-Allow-Methods").isEmpty()); 96 | Assert.assertEquals("OPTIONS,GET", responseEntity.getHeaders().get("Access-Control-Allow-Methods").get(0)); 97 | Assert.assertFalse(responseEntity.getHeaders().get("Access-Control-Allow-Headers").isEmpty()); 98 | Assert.assertEquals("Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token", responseEntity.getHeaders().get("Access-Control-Allow-Headers").get(0)); 99 | } 100 | 101 | @Test 102 | public void shouldShowAllTodos() { 103 | TodoInfo todoToSave = new TodoInfo("1st todo"); 104 | 105 | assertTodoContains(createAndWaitForView(todoToSave), 106 | Arrays.asList(getTodos().getBody()) 107 | ); 108 | } 109 | 110 | @Test 111 | public void shouldDeleteSingleTodo() throws InterruptedException { 112 | TodoInfo todoToSave = new TodoInfo("a todo"); 113 | Todo todo = createAndWaitForView(todoToSave); 114 | 115 | restTemplate.delete(baseUrl("todos/" + todo.getId())); 116 | 117 | awaitNotFoundResponse(idx -> getTodo(todo.getId())); 118 | } 119 | 120 | @Test 121 | public void shouldCreateNewTodo() throws InterruptedException { 122 | TodoInfo todoToSave = new TodoInfo("walk the dog"); 123 | Todo todoView = createAndWaitForView(todoToSave); 124 | 125 | Todo expectedTodo = makeExpectedTodo(todoView.getId(), todoToSave); 126 | 127 | assertTodoEquals(expectedTodo, todoView); 128 | } 129 | 130 | @Test 131 | public void shouldUpdateTodo() throws InterruptedException { 132 | 133 | TodoInfo todoToSave = new TodoInfo("todo 1"); 134 | String todoId = createAndWaitForView(todoToSave).getId(); 135 | 136 | TodoInfo todoWithChanges = new TodoInfo(); 137 | todoWithChanges.setTitle("todo 2"); 138 | todoWithChanges.setCompleted(true); 139 | todoWithChanges.setOrder(42); 140 | 141 | ResponseEntity putResult = updateTodo(todoId, todoWithChanges); 142 | 143 | Todo expectedTodo = makeExpectedTodo(todoId, todoWithChanges); 144 | 145 | Todo updatedTodo = putResult.getBody(); 146 | assertTodoEquals(expectedTodo, updatedTodo); 147 | 148 | Todo updatedTodoInView = awaitSuccessfulRequest( 149 | () -> getTodo(todoId), 150 | re -> re.getTitle().equals(todoWithChanges.getTitle()) 151 | ); 152 | 153 | assertTodoEquals(expectedTodo, updatedTodoInView); 154 | 155 | } 156 | 157 | protected abstract String getHost(); 158 | } 159 | 160 | -------------------------------------------------------------------------------- /test-utils/src/main/java/net/chrisrichardson/eventstore/examples/todolist/testutil/BasicWebTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.testutil; 2 | 3 | 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.apache.http.client.HttpClient; 6 | import org.apache.http.impl.client.HttpClients; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.boot.autoconfigure.web.HttpMessageConverters; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 13 | import org.springframework.http.converter.HttpMessageConverter; 14 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 15 | import org.springframework.web.client.RestTemplate; 16 | 17 | import java.util.Collections; 18 | 19 | 20 | @Configuration 21 | @EnableAutoConfiguration 22 | public class BasicWebTestConfiguration { 23 | 24 | @Bean 25 | public RestTemplate restTemplate(HttpMessageConverters converters) { 26 | 27 | // we have to define Apache HTTP client to use the PATCH verb 28 | MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 29 | converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/json")); 30 | converter.setObjectMapper(new ObjectMapper()); 31 | 32 | HttpClient httpClient = HttpClients.createDefault(); 33 | RestTemplate restTemplate = new RestTemplate(Collections.>singletonList(converter)); 34 | restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)); 35 | 36 | restTemplate.setErrorHandler(new RestTemplateErrorHandler()); 37 | 38 | return restTemplate; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test-utils/src/main/java/net/chrisrichardson/eventstore/examples/todolist/testutil/RestTemplateErrorHandler.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.testutil; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.client.ClientHttpResponse; 6 | import org.springframework.web.client.ResponseErrorHandler; 7 | 8 | import java.io.IOException; 9 | 10 | 11 | public class RestTemplateErrorHandler implements ResponseErrorHandler { 12 | 13 | private static final Logger log = LoggerFactory.getLogger(RestTemplateErrorHandler.class); 14 | 15 | @Override 16 | public void handleError(ClientHttpResponse response) throws IOException { 17 | log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText()); 18 | } 19 | 20 | @Override 21 | public boolean hasError(ClientHttpResponse response) throws IOException { 22 | return RestUtil.isError(response.getStatusCode()); 23 | } 24 | } -------------------------------------------------------------------------------- /test-utils/src/main/java/net/chrisrichardson/eventstore/examples/todolist/testutil/RestUtil.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.testutil; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | 6 | public class RestUtil { 7 | 8 | public static boolean isError(HttpStatus status) { 9 | HttpStatus.Series series = status.series(); 10 | return (HttpStatus.Series.CLIENT_ERROR.equals(series) 11 | || HttpStatus.Series.SERVER_ERROR.equals(series)); 12 | } 13 | } -------------------------------------------------------------------------------- /test-utils/src/main/java/net/chrisrichardson/eventstore/examples/todolist/testutil/TestUtil.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.testutil; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | import rx.Observable; 6 | import rx.functions.Func1; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.function.Supplier; 11 | 12 | 13 | public class TestUtil { 14 | 15 | public static T awaitSuccessfulRequest(Supplier> func, Func1 predicate) { 16 | try { 17 | return Observable.interval(400, TimeUnit.MILLISECONDS) 18 | .take(50) 19 | .map(x -> func.get()) 20 | .filter(re -> re.getStatusCode().equals(HttpStatus.OK) && re.getBody() != null && predicate.call(re.getBody())) 21 | .toBlocking().first().getBody(); 22 | } catch (Exception e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | public static ResponseEntity awaitNotFoundResponse(Func1 func) { 28 | try { 29 | return Observable.interval(400, TimeUnit.MILLISECONDS) 30 | .take(50) 31 | .map(func) 32 | .filter(re -> re.getStatusCode().equals(HttpStatus.NOT_FOUND)) 33 | .toBlocking().first(); 34 | } catch (Exception e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | 39 | public static T awaitSuccessfulRequest(Supplier> func) { 40 | try { 41 | return Observable.interval(400, TimeUnit.MILLISECONDS) 42 | .take(50) 43 | .map(x -> func.get()) 44 | .filter(re -> re.getStatusCode().equals(HttpStatus.OK) && re.getBody() != null) 45 | .toBlocking().first().getBody(); 46 | } catch (Exception e) { 47 | throw new RuntimeException(e); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /todo-command-side/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion" 4 | 5 | testCompile "junit:junit:4.11" 6 | } -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/TodoBackendConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.sync.EventuateAggregateStore; 5 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.TodoCommand; 6 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoAggregate; 7 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoBulkDeleteAggregate; 8 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoService; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | 13 | @Configuration 14 | public class TodoBackendConfiguration { 15 | 16 | @Bean 17 | public AggregateRepository aggregateRepository(EventuateAggregateStore eventStore) { 18 | return new AggregateRepository<>(TodoAggregate.class, eventStore); 19 | } 20 | 21 | @Bean 22 | public AggregateRepository bulkDeleteAggregateRepository(EventuateAggregateStore eventStore) { 23 | return new AggregateRepository<>(TodoBulkDeleteAggregate.class, eventStore); 24 | } 25 | 26 | @Bean 27 | public TodoService updateService(AggregateRepository aggregateRepository, AggregateRepository bulkDeleteAggregateRepository) { 28 | return new TodoService(aggregateRepository, bulkDeleteAggregateRepository); 29 | } 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/CreateTodoCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | 4 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 5 | 6 | public class CreateTodoCommand implements TodoCommand { 7 | 8 | private TodoInfo todo; 9 | 10 | public CreateTodoCommand(TodoInfo todo) { 11 | this.todo = todo; 12 | } 13 | 14 | public TodoInfo getTodo() { 15 | return todo; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/DeleteAllTodoCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | 4 | public class DeleteAllTodoCommand implements TodoCommand { 5 | } 6 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/DeleteTodoCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | 4 | public class DeleteTodoCommand implements TodoCommand { 5 | } 6 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/DeleteTodosCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class DeleteTodosCommand implements TodoCommand { 7 | private List ids; 8 | 9 | public DeleteTodosCommand() { 10 | } 11 | 12 | public DeleteTodosCommand(List ids) { 13 | this.ids = ids; 14 | } 15 | 16 | public List getIds() { 17 | return ids; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/TodoCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | 4 | import io.eventuate.Command; 5 | 6 | public interface TodoCommand extends Command { 7 | } 8 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/command/UpdateTodoCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command; 2 | 3 | 4 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 5 | 6 | 7 | public class UpdateTodoCommand implements TodoCommand { 8 | private String id; 9 | private TodoInfo todo; 10 | 11 | public UpdateTodoCommand(String id, TodoInfo todo) { 12 | this.id = id; 13 | this.todo = todo; 14 | } 15 | 16 | public String getId() { 17 | return id; 18 | } 19 | 20 | public TodoInfo getTodo() { 21 | return todo; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/domain/TodoAggregate.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain; 2 | 3 | 4 | import io.eventuate.Event; 5 | import io.eventuate.EventUtil; 6 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate; 7 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoCreatedEvent; 8 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoDeletedEvent; 9 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoUpdatedEvent; 10 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 11 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.CreateTodoCommand; 12 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.DeleteTodoCommand; 13 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.TodoCommand; 14 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.UpdateTodoCommand; 15 | 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | public class TodoAggregate extends ReflectiveMutableCommandProcessingAggregate { 20 | 21 | private TodoInfo todo; 22 | private boolean deleted; 23 | 24 | public List process(CreateTodoCommand cmd) { 25 | if (this.deleted) { 26 | return Collections.emptyList(); 27 | } 28 | return EventUtil.events(new TodoCreatedEvent(cmd.getTodo())); 29 | } 30 | 31 | public List process(UpdateTodoCommand cmd) { 32 | if (this.deleted) { 33 | return Collections.emptyList(); 34 | } 35 | return EventUtil.events(new TodoUpdatedEvent(cmd.getTodo())); 36 | } 37 | 38 | public List process(DeleteTodoCommand cmd) { 39 | if (this.deleted) { 40 | return Collections.emptyList(); 41 | } 42 | return EventUtil.events(new TodoDeletedEvent()); 43 | } 44 | 45 | 46 | public void apply(TodoCreatedEvent event) { 47 | this.todo = event.getTodo(); 48 | } 49 | 50 | public void apply(TodoUpdatedEvent event) { 51 | this.todo = event.getTodo(); 52 | } 53 | 54 | public void apply(TodoDeletedEvent event) { 55 | this.deleted = true; 56 | } 57 | 58 | public TodoInfo getTodo() { 59 | return todo; 60 | } 61 | 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/domain/TodoBulkDeleteAggregate.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain; 2 | 3 | 4 | import io.eventuate.Event; 5 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate; 6 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoDeletionRequestedEvent; 7 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.DeleteTodosCommand; 8 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.TodoCommand; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | 14 | public class TodoBulkDeleteAggregate extends ReflectiveMutableCommandProcessingAggregate { 15 | 16 | public List process(DeleteTodosCommand cmd) { 17 | return cmd.getIds() 18 | .stream() 19 | .map(TodoDeletionRequestedEvent::new) 20 | .collect(Collectors.toList()); 21 | } 22 | 23 | public void apply(TodoDeletionRequestedEvent event) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /todo-command-side/src/main/java/net/chrisrichardson/eventstore/examples/todolist/todoservice/backend/domain/TodoService.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 6 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.command.*; 7 | 8 | import java.util.List; 9 | 10 | 11 | public class TodoService { 12 | 13 | private final AggregateRepository aggregateRepository; 14 | private final AggregateRepository bulkDeleteAggregateRepository; 15 | 16 | 17 | public TodoService(AggregateRepository todoRepository, AggregateRepository bulkDeleteAggregateRepository) { 18 | this.aggregateRepository = todoRepository; 19 | this.bulkDeleteAggregateRepository = bulkDeleteAggregateRepository; 20 | } 21 | 22 | public EntityWithIdAndVersion save(TodoInfo todo) { 23 | return aggregateRepository.save(new CreateTodoCommand(todo)); 24 | } 25 | 26 | public EntityWithIdAndVersion remove(String id) { 27 | return aggregateRepository.update(id, new DeleteTodoCommand()); 28 | } 29 | 30 | public EntityWithIdAndVersion update(String id, TodoInfo newTodo) { 31 | return aggregateRepository.update(id, new UpdateTodoCommand(id, newTodo)); 32 | } 33 | 34 | public EntityWithIdAndVersion deleteAll(List ids) { 35 | return bulkDeleteAggregateRepository.save(new DeleteTodosCommand(ids)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /todo-commandside-lambda/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":todo-dynamodb") 3 | compile project(":todo-command-side") 4 | compile "com.amazonaws:aws-lambda-java-core:1.1.0" 5 | compile "org.springframework.boot:spring-boot:$springBootVersion" 6 | 7 | compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion" 8 | compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion" 9 | } 10 | 11 | task buildZip(type: Zip) { 12 | from compileJava 13 | from processResources 14 | into('lib') { 15 | from configurations.runtime 16 | } 17 | } 18 | 19 | build.dependsOn buildZip -------------------------------------------------------------------------------- /todo-commandside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/commandside/CommandsideTodoHandler.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.commandside; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import io.eventuate.javaclient.commonimpl.JSonMapper; 6 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractAWSTodoHandler; 7 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayRequest; 8 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse; 9 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 10 | import net.chrisrichardson.eventstore.examples.todolist.model.Todo; 11 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 12 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoAggregate; 13 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoService; 14 | 15 | import java.util.stream.Collectors; 16 | 17 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.applicationJsonHeaders; 18 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.build405ErrorResponse; 19 | 20 | public class CommandsideTodoHandler extends AbstractAWSTodoHandler { 21 | 22 | private TodoService todoService; 23 | private TodoDAO todoDAO; 24 | 25 | public CommandsideTodoHandler(TodoService todoService, TodoDAO todoDAO) { 26 | this.todoService = todoService; 27 | this.todoDAO = todoDAO; 28 | } 29 | 30 | @Override 31 | protected ApiGatewayResponse handleAWSRequest(ApiGatewayRequest request, Context context) { 32 | Object response = null; 33 | 34 | switch (request.getHttpMethod()) { 35 | case "POST": { 36 | response = handleTodoCreate(request); 37 | break; 38 | } 39 | case "PUT": { 40 | response = handlerTodoUpdate(request); 41 | break; 42 | } 43 | case "DELETE": { 44 | response = handlerTodoDelete(request); 45 | break; 46 | } 47 | default: { 48 | build405ErrorResponse(); 49 | } 50 | } 51 | 52 | return ApiGatewayResponse.builder() 53 | .setStatusCode(200) 54 | .setObjectBody(response) 55 | .setHeaders(applicationJsonHeaders()) 56 | .build(); 57 | } 58 | 59 | private Object handleTodoCreate(ApiGatewayRequest input) { 60 | TodoInfo todoInfo = JSonMapper.fromJson(input.getBody(), TodoInfo.class); 61 | EntityWithIdAndVersion entityWithIdAndVersion = todoService.save(todoInfo); 62 | return new Todo(entityWithIdAndVersion.getEntityId(), 63 | entityWithIdAndVersion.getAggregate().getTodo()); 64 | } 65 | 66 | private Object handlerTodoUpdate(ApiGatewayRequest input) { 67 | String todoId = input.getPathParameters().get("todoId"); 68 | TodoInfo todoInfo = JSonMapper.fromJson(input.getBody(), TodoInfo.class); 69 | 70 | EntityWithIdAndVersion entityWithIdAndVersion = todoService.update(todoId, todoInfo); 71 | return new Todo(todoId, 72 | entityWithIdAndVersion.getAggregate().getTodo()); 73 | } 74 | 75 | private Object handlerTodoDelete(ApiGatewayRequest input) { 76 | String todoId = input.getPathParameters().get("todoId"); 77 | 78 | if (todoId != null) { 79 | EntityWithIdAndVersion entityWithIdAndVersion = todoService.remove(todoId); 80 | return new Todo(todoId, 81 | entityWithIdAndVersion.getAggregate().getTodo()); 82 | } else { 83 | todoService.deleteAll(todoDAO.getAll() 84 | .stream().map(net.chrisrichardson.eventstore.examples.todolist.db.Todo::getId) 85 | .collect(Collectors.toList()) 86 | ); 87 | return "All todo records were successfully deleted"; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /todo-commandside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/commandside/CommandsideTodoHandlerConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.commandside; 2 | 3 | import io.eventuate.javaclient.driver.EventuateDriverConfiguration; 4 | import net.chrisrichardson.eventstore.examples.todolist.db.DatabaseTodoConfiguration; 5 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 6 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.TodoBackendConfiguration; 7 | import net.chrisrichardson.eventstore.examples.todolist.todoservice.backend.domain.TodoService; 8 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Import; 12 | import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; 13 | 14 | @Configuration 15 | @Import({TodoBackendConfiguration.class, 16 | EventuateDriverConfiguration.class, 17 | DatabaseTodoConfiguration.class}) 18 | @EnableAutoConfiguration 19 | public class CommandsideTodoHandlerConfiguration { 20 | 21 | @Bean 22 | public CommandsideTodoHandler commandsideTodoHandler(TodoService todoService, TodoDAO todoDAO) { 23 | return new CommandsideTodoHandler(todoService, todoDAO); 24 | } 25 | 26 | @Bean 27 | public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { 28 | System.setProperty("vertx.disableFileCPResolving", "true"); 29 | return new PropertySourcesPlaceholderConfigurer(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /todo-commandside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/commandside/CommandsideTodoLambda.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.commandside; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractHttpLambda; 4 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayRequest; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | 7 | public class CommandsideTodoLambda extends AbstractHttpLambda { 8 | 9 | public CommandsideTodoLambda() { 10 | super(new AnnotationConfigApplicationContext(CommandsideTodoHandlerConfiguration.class)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /todo-dynamodb/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile "commons-lang:commons-lang:2.6" 4 | compile 'com.amazonaws:aws-java-sdk-sts:1.11.119' 5 | compile "com.amazonaws:aws-java-sdk-dynamodb:$awsJavaSdkVersion" 6 | compile "org.springframework.boot:spring-boot:$springBootVersion" 7 | 8 | testCompile "junit:junit:4.11" 9 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | } 11 | -------------------------------------------------------------------------------- /todo-dynamodb/src/main/java/net/chrisrichardson/eventstore/examples/todolist/db/DatabaseTodoConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.db; 2 | 3 | 4 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; 5 | import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; 6 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; 7 | import com.amazonaws.services.dynamodbv2.document.DynamoDB; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class DatabaseTodoConfiguration { 13 | 14 | // DynamoDB configuration 15 | @Bean 16 | public AmazonDynamoDB amazonDynamoDB() { 17 | AmazonDynamoDB amazonDynamoDB = AmazonDynamoDBClientBuilder.defaultClient(); 18 | return amazonDynamoDB; 19 | } 20 | 21 | @Bean 22 | public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB) { 23 | return new DynamoDBMapper(amazonDynamoDB); 24 | } 25 | 26 | @Bean 27 | public DynamoDB dynamoDB(AmazonDynamoDB amazonDynamoDB) { 28 | return new DynamoDB(amazonDynamoDB); 29 | } 30 | 31 | @Bean 32 | public TodoDAO routesDAO(DynamoDBMapper dynamoDBMapper) { 33 | return new TodoDAO(dynamoDBMapper); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /todo-dynamodb/src/main/java/net/chrisrichardson/eventstore/examples/todolist/db/Todo.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.db; 2 | 3 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute; 4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey; 5 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable; 6 | import net.chrisrichardson.eventstore.examples.todolist.model.TodoInfo; 7 | import org.apache.commons.lang.builder.EqualsBuilder; 8 | 9 | @DynamoDBTable(tableName = "todo_records") 10 | public class Todo { 11 | @DynamoDBHashKey 12 | private String id; 13 | @DynamoDBAttribute 14 | private String title; 15 | @DynamoDBAttribute 16 | private Boolean completed; 17 | @DynamoDBAttribute 18 | private Integer order; 19 | 20 | public Todo() { 21 | } 22 | 23 | public Todo(String id) { 24 | this.id = id; 25 | } 26 | 27 | 28 | public Todo(TodoInfo todoInfo) { 29 | this.title = todoInfo.getTitle(); 30 | this.completed = todoInfo.isCompleted(); 31 | this.order = todoInfo.getOrder(); 32 | } 33 | 34 | public Todo(String id, String title, Boolean completed, Integer order) { 35 | this.id = id; 36 | this.title = title; 37 | this.completed = completed; 38 | this.order = order; 39 | } 40 | 41 | public String getTitle() { 42 | return title; 43 | } 44 | 45 | public void setTitle(String title) { 46 | this.title = title; 47 | } 48 | 49 | public String getId() { 50 | return id; 51 | } 52 | 53 | public void setId(String id) { 54 | this.id = id; 55 | } 56 | 57 | public boolean isCompleted() { 58 | return nonNull(completed, false); 59 | } 60 | 61 | public void setCompleted(boolean completed) { 62 | this.completed = completed; 63 | } 64 | 65 | public Integer getOrder() { 66 | return nonNull(order, 0); 67 | } 68 | 69 | public void setOrder(Integer order) { 70 | this.order = order; 71 | } 72 | 73 | public Todo merge(Todo newTodo) { 74 | return new Todo(id, 75 | nonNull(newTodo.title, title), 76 | nonNull(newTodo.completed, completed), 77 | nonNull(newTodo.order, order)); 78 | } 79 | 80 | private T nonNull(T value, T defaultValue) { 81 | return value == null ? defaultValue : value; 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | return EqualsBuilder.reflectionEquals(this, o); 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "Todo{" + 92 | "id=" + id + 93 | ", title='" + title + '\'' + 94 | ", completed=" + completed + 95 | ", order=" + order + 96 | '}'; 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /todo-dynamodb/src/main/java/net/chrisrichardson/eventstore/examples/todolist/db/TodoDAO.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.db; 2 | 3 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; 4 | import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.stream.Collectors; 9 | 10 | public class TodoDAO { 11 | 12 | private DynamoDBMapper mapper; 13 | 14 | public TodoDAO(DynamoDBMapper mapper) { 15 | this.mapper = mapper; 16 | } 17 | 18 | public Optional findOne(String todoId) { 19 | return Optional.ofNullable(mapper.load(Todo.class, todoId)); 20 | } 21 | 22 | public List getAll() { 23 | return mapper.scan(Todo.class, new DynamoDBScanExpression()); 24 | } 25 | 26 | public void save(Todo todo) { 27 | mapper.save(todo); 28 | } 29 | 30 | public void remove(String todoId) { 31 | mapper.delete(new Todo(todoId)); 32 | } 33 | 34 | public void removeAll(List todoIds) { 35 | mapper.batchDelete(todoIds.stream().map(Todo::new).collect(Collectors.toList())); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /todo-find-lambda/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile project(":todo-dynamodb") 4 | compile "com.amazonaws:aws-lambda-java-core:1.1.0" 5 | compile "org.springframework.boot:spring-boot:$springBootVersion" 6 | } 7 | 8 | task buildZip(type: Zip) { 9 | from compileJava 10 | from processResources 11 | into('lib') { 12 | from configurations.runtime 13 | } 14 | } 15 | 16 | build.dependsOn buildZip -------------------------------------------------------------------------------- /todo-find-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/findlambda/FindTodoHandler.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.findlambda; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractAWSTodoHandler; 5 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayRequest; 6 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse; 7 | import net.chrisrichardson.eventstore.examples.todolist.db.Todo; 8 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 9 | 10 | import java.util.Optional; 11 | 12 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.applicationJsonHeaders; 13 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.build404ErrorResponse; 14 | 15 | public class FindTodoHandler extends AbstractAWSTodoHandler { 16 | 17 | private TodoDAO todoDAO; 18 | 19 | public FindTodoHandler(TodoDAO todoDAO) { 20 | this.todoDAO = todoDAO; 21 | } 22 | 23 | @Override 24 | protected ApiGatewayResponse handleAWSRequest(ApiGatewayRequest request, Context context) { 25 | String todoId = Optional.ofNullable(request.getPathParameters()) 26 | .map(paramsMap -> paramsMap.get("todoId")) 27 | .orElse(null); 28 | 29 | ApiGatewayResponse.Builder responseBuilder = ApiGatewayResponse.builder() 30 | .setStatusCode(200) 31 | .setHeaders(applicationJsonHeaders()); 32 | if (todoId != null) { 33 | Optional todo = todoDAO.findOne(todoId); 34 | if (todo.isPresent()) { 35 | responseBuilder.setObjectBody(todo.get()); 36 | } else { 37 | return build404ErrorResponse(); 38 | } 39 | } else { 40 | responseBuilder.setObjectBody(todoDAO.getAll()); 41 | } 42 | return responseBuilder.build(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /todo-find-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/findlambda/FindTodoHandlerConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.findlambda; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.db.DatabaseTodoConfiguration; 4 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({DatabaseTodoConfiguration.class}) 12 | @EnableAutoConfiguration 13 | public class FindTodoHandlerConfiguration { 14 | 15 | @Bean 16 | public FindTodoHandler findTodoHandler(TodoDAO todoDAO) { 17 | return new FindTodoHandler(todoDAO); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /todo-find-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/findlambda/FindTodoLambda.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.findlambda; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractHttpLambda; 4 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayRequest; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | 7 | public class FindTodoLambda extends AbstractHttpLambda { 8 | public FindTodoLambda() { 9 | super(new AnnotationConfigApplicationContext(FindTodoHandlerConfiguration.class)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo-queryside-lambda/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile project(":eventhandler-common") 4 | compile project(":todo-dynamodb") 5 | compile "io.eventuate.client.java:eventuate-client-java-common-impl:$eventuateClientVersion" 6 | } 7 | 8 | task buildZip(type: Zip) { 9 | from compileJava 10 | from processResources 11 | into('lib') { 12 | from configurations.runtime 13 | } 14 | } 15 | 16 | build.dependsOn buildZip -------------------------------------------------------------------------------- /todo-queryside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/queryside/QuerysideTodoHandler.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.queryside; 2 | 3 | import com.amazonaws.services.lambda.runtime.Context; 4 | import io.eventuate.javaclient.commonimpl.JSonMapper; 5 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractAWSTodoHandler; 6 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse; 7 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoCreatedEvent; 8 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoDeletedEvent; 9 | import net.chrisrichardson.eventstore.examples.todolist.common.event.TodoUpdatedEvent; 10 | import net.chrisrichardson.eventstore.examples.todolist.db.Todo; 11 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 12 | import net.chrisrichardson.eventstore.examples.todolist.eventhandler.common.EventsRequest; 13 | 14 | import static net.chrisrichardson.eventstore.examples.todolist.common.aws.ApiGatewayResponse.applicationJsonHeaders; 15 | 16 | public class QuerysideTodoHandler extends AbstractAWSTodoHandler { 17 | 18 | private TodoDAO todoDAO; 19 | 20 | public QuerysideTodoHandler(TodoDAO todoDAO) { 21 | this.todoDAO = todoDAO; 22 | } 23 | 24 | @Override 25 | protected ApiGatewayResponse handleAWSRequest(EventsRequest input, Context context) { 26 | input.getEvents().forEach(eventRecord -> { 27 | String entityId = eventRecord.getEntityId(); 28 | if (eventRecord.isEventType(TodoCreatedEvent.class)) { 29 | TodoCreatedEvent event = JSonMapper.fromJson(eventRecord.getEventData(), TodoCreatedEvent.class); 30 | handleCreateEvent(event, entityId); 31 | } else if (eventRecord.isEventType(TodoUpdatedEvent.class)) { 32 | TodoUpdatedEvent event = JSonMapper.fromJson(eventRecord.getEventData(), TodoUpdatedEvent.class); 33 | handleUpdateEvent(event, entityId); 34 | } else if (eventRecord.isEventType(TodoDeletedEvent.class)) { 35 | handleDeleteEvent(entityId); 36 | } 37 | }); 38 | return ApiGatewayResponse.builder() 39 | .setObjectBody("Event had been successfully processed") 40 | .setHeaders(applicationJsonHeaders()) 41 | .build(); 42 | } 43 | 44 | private void handleCreateEvent(TodoCreatedEvent event, String entityId) { 45 | Todo todo = new Todo(event.getTodo()); 46 | todo.setId(entityId); 47 | todoDAO.save(todo); 48 | } 49 | 50 | private void handleUpdateEvent(TodoUpdatedEvent event, String entityId) { 51 | Todo todo = new Todo(event.getTodo()); 52 | todo.setId(entityId); 53 | todoDAO.save(todo); 54 | } 55 | 56 | private void handleDeleteEvent(String entityId) { 57 | todoDAO.remove(entityId); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /todo-queryside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/queryside/QuerysideTodoHandlerConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.queryside; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.db.DatabaseTodoConfiguration; 4 | import net.chrisrichardson.eventstore.examples.todolist.db.TodoDAO; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({DatabaseTodoConfiguration.class}) 12 | @EnableAutoConfiguration 13 | public class QuerysideTodoHandlerConfiguration { 14 | 15 | @Bean 16 | public QuerysideTodoHandler querysideTodoHandler(TodoDAO todoDAO) { 17 | return new QuerysideTodoHandler(todoDAO); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /todo-queryside-lambda/src/main/java/net/chrisrichardson/eventstore/examples/todolist/queryside/QuerysideTodoLambda.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.todolist.queryside; 2 | 3 | import net.chrisrichardson.eventstore.examples.todolist.common.aws.AbstractHttpLambda; 4 | import net.chrisrichardson.eventstore.examples.todolist.eventhandler.common.EventsRequest; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | 7 | public class QuerysideTodoLambda extends AbstractHttpLambda { 8 | 9 | public QuerysideTodoLambda() { 10 | super(new AnnotationConfigApplicationContext(QuerysideTodoHandlerConfiguration.class)); 11 | } 12 | } 13 | --------------------------------------------------------------------------------