Compare commits
177 Commits
wip-vagran
...
wip-eventu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ae6db7229 | ||
|
|
c03a5fed8d | ||
|
|
ee45163f2c | ||
|
|
e6bf638b4b | ||
|
|
d6f60101db | ||
|
|
e54577d656 | ||
|
|
bae00f6bd7 | ||
|
|
68dca23a6b | ||
|
|
19c9f88a7f | ||
|
|
9b6956b8df | ||
|
|
e2de325df2 | ||
|
|
c9fa916cdd | ||
|
|
f79ebb4d18 | ||
|
|
8f2fc83a34 | ||
|
|
05f2f309e7 | ||
|
|
6cf774da2a | ||
|
|
3c2e9d374c | ||
|
|
55cb34ef4f | ||
|
|
dde554e442 | ||
|
|
fd75779093 | ||
|
|
c8291bec71 | ||
|
|
1005c47d83 | ||
|
|
47e9053285 | ||
|
|
f76912a6cf | ||
|
|
625ea6007e | ||
|
|
6fae59fdeb | ||
|
|
f846a32d95 | ||
|
|
2a712017f1 | ||
|
|
85613936f4 | ||
|
|
6f480ad11a | ||
|
|
2b0c405378 | ||
|
|
a5d1e7312c | ||
|
|
29d42fda9a | ||
|
|
1b53bd9147 | ||
|
|
d1328e4ce8 | ||
|
|
45bda8e14d | ||
|
|
d9e13ff669 | ||
|
|
e14787bce8 | ||
|
|
1d14ece9cf | ||
|
|
1e13d482a8 | ||
|
|
2467099c3e | ||
|
|
f4ec33d275 | ||
|
|
a91ade08b1 | ||
|
|
fb97767a06 | ||
|
|
1e7234166a | ||
|
|
ef444bde9b | ||
|
|
4b73b9beed | ||
|
|
d0bdd51406 | ||
|
|
b0b32d0a3f | ||
|
|
e9517fe30f | ||
|
|
f4e070e7bd | ||
|
|
5f8475d675 | ||
|
|
12e46582cd | ||
|
|
b0855ebd81 | ||
|
|
39309f23a1 | ||
|
|
bd3de1a938 | ||
|
|
f570ccbe90 | ||
|
|
fe0ce037de | ||
|
|
1e40b2a591 | ||
|
|
f1d97ac49c | ||
|
|
b2b68ce163 | ||
|
|
e486102018 | ||
|
|
e488df3d06 | ||
|
|
4f11433390 | ||
|
|
b573027fc2 | ||
|
|
3117f12402 | ||
|
|
5b029d8307 | ||
|
|
6d8376cfd5 | ||
|
|
f59f4c78dd | ||
|
|
4b3fe001e7 | ||
|
|
76f3c830af | ||
|
|
62d41e9d5b | ||
|
|
7748217973 | ||
|
|
9a9511b2c4 | ||
|
|
28eefb81ab | ||
|
|
70b552a961 | ||
|
|
2c5b5b4132 | ||
|
|
e3c0112e8f | ||
|
|
1a5442a060 | ||
|
|
0b81ae8c08 | ||
|
|
b6b1fb7f0f | ||
|
|
e3dddcbc7b | ||
|
|
e699220162 | ||
|
|
fe89adba09 | ||
|
|
a369c4989f | ||
|
|
dd0bb4551b | ||
|
|
888544b700 | ||
|
|
bf01ad8e00 | ||
|
|
faa4027305 | ||
|
|
d55ff55e96 | ||
|
|
bdb4d3db26 | ||
|
|
9276b1bdc8 | ||
|
|
23fba9e462 | ||
|
|
05ebfd7d14 | ||
|
|
7b54b9042d | ||
|
|
7c6328aa5e | ||
|
|
9967ad9c52 | ||
|
|
8f86f72d85 | ||
|
|
bdcdae862d | ||
|
|
bfeb2e2e16 | ||
|
|
b125b30304 | ||
|
|
febef227fd | ||
|
|
40e03dd8e6 | ||
|
|
63d0e21fc4 | ||
|
|
6409ed1ab9 | ||
|
|
710487ebf9 | ||
|
|
df0d391521 | ||
|
|
6d53f149c8 | ||
|
|
3a4ee4e90c | ||
|
|
5001127978 | ||
|
|
ba5077685e | ||
|
|
b16039ee53 | ||
|
|
c11f1b1a64 | ||
|
|
f4ecc093fe | ||
|
|
afa3cf0042 | ||
|
|
28216a082d | ||
|
|
5511d1318e | ||
|
|
c5778b1379 | ||
|
|
21d21b9833 | ||
|
|
4bc8e8408d | ||
|
|
91361a1b18 | ||
|
|
fb1069ebb9 | ||
|
|
e337f05c89 | ||
|
|
725814407d | ||
|
|
0a38ccd09e | ||
|
|
f887c9fefe | ||
|
|
587a9164b7 | ||
|
|
b07933121d | ||
|
|
98c01cceeb | ||
|
|
d1339729ec | ||
|
|
99d07667bb | ||
|
|
d36c01b331 | ||
|
|
c61cc82d0a | ||
|
|
65524286da | ||
|
|
99000f04ed | ||
|
|
7b2fcc8a7c | ||
|
|
8615ac1d1e | ||
|
|
8d1faedef3 | ||
|
|
84debbda28 | ||
|
|
f74196e1f2 | ||
|
|
a73fc2477b | ||
|
|
f14fd73f63 | ||
|
|
064151fa61 | ||
|
|
43916e10aa | ||
|
|
ffeb521e3b | ||
|
|
1e2e564c29 | ||
|
|
655f86071c | ||
|
|
ff00467b47 | ||
|
|
28147a707d | ||
|
|
e8e147e3eb | ||
|
|
8228738710 | ||
|
|
8c342e40ce | ||
|
|
42db215414 | ||
|
|
23ffa96f47 | ||
|
|
822c0efb6d | ||
|
|
5108bc1b27 | ||
|
|
8564ec3fb2 | ||
|
|
a217f1b1e6 | ||
|
|
92526f2780 | ||
|
|
8ab13e580b | ||
|
|
a095076c0c | ||
|
|
fc2bd9970b | ||
|
|
a5994ad496 | ||
|
|
bfd04ef726 | ||
|
|
5c85418cc4 | ||
|
|
c0a9d6ed7d | ||
|
|
92d0940222 | ||
|
|
7f1f2af188 | ||
|
|
51e9d6c7fa | ||
|
|
cd35ac3d31 | ||
|
|
5d7e56578f | ||
|
|
59ecaa804a | ||
|
|
06f14a54bf | ||
|
|
afb7c9bc49 | ||
|
|
e0691a61a2 | ||
|
|
cbee50658e | ||
|
|
85f8826741 |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -25,6 +25,12 @@ browserapp/reports
|
||||
loginapp/reports
|
||||
local_developer_config
|
||||
|
||||
js-frontend/node_modules
|
||||
js-frontend/build
|
||||
js-frontend/dist
|
||||
js-frontend/dist-intermediate
|
||||
|
||||
|
||||
|
||||
/web/web.log
|
||||
*.log
|
||||
|
||||
18
README.md
18
README.md
@@ -1,7 +1,7 @@
|
||||
#Event-Sourcing+CQRS example application
|
||||
|
||||
This example application is the money transfer application described in my talk [Building and deploying microservices with event sourcing, CQRS and Docker](http://plainoldobjects.com/presentations/building-and-deploying-microservices-with-event-sourcing-cqrs-and-docker/).
|
||||
This talk describe a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
|
||||
This talk describes a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
|
||||
event sourcing (ES) and command query responsibility segregation (CQRS).
|
||||
Applications consist of loosely coupled components that communicate using events.
|
||||
These components can be deployed either as separate services or packaged as a monolithic application for simplified development and testing.
|
||||
@@ -94,9 +94,12 @@ First, you need to tell the query side code how to connect to MongoDB:
|
||||
```
|
||||
|
||||
[Docker Compose](https://docs.docker.com/compose/) is a great way to run MongoDB.
|
||||
You can run the `docker-compose up -d mongodb` to run MongoDB.
|
||||
You can run the `docker-compose up -d mongodb` to run MongoDB and then set `SPRING_DATA_MONGODB_URI` as follows:
|
||||
```
|
||||
export SPRING_DATA_MONGODB_URI=mongodb://$(docker-machine ip default)/yourdb
|
||||
```
|
||||
|
||||
Second, some of the tests in accounts-command-side-service, transactions-command-side-service, accounts-query-side-service and e2e-test need you need to set some environment variables that tell them how to connect to the Event Store server.
|
||||
Second, some of the tests in accounts-command-side-service, transactions-command-side-service, accounts-query-side-service and e2e-test require you to set some environment variables that tell them how to connect to the Event Store server.
|
||||
But don't worry.
|
||||
The build is configured to ignore failures for those projects.
|
||||
|
||||
@@ -117,6 +120,15 @@ Simply use this command:
|
||||
java -jar monolithic-service/build/libs/monolithic-service.jar
|
||||
```
|
||||
|
||||
This will start the service running on port 8080 (you can change using the --server.port=9999 option).
|
||||
|
||||
Once the service has started you can open the Swagger UI: http://localhost:8080/swagger-ui.html.
|
||||
You can then:
|
||||
|
||||
1. Create two accounts (save the account ids)
|
||||
2. Create a money transfer
|
||||
3. View the updated account balances
|
||||
|
||||
## Running the microservices
|
||||
|
||||
The other option is to run the services separately.
|
||||
|
||||
@@ -6,6 +6,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
DOCKER_COMPOSE="docker-compose -p event-sourcing-examples"
|
||||
|
||||
if [ "$1" = "-f" ] ; then
|
||||
shift;
|
||||
DOCKER_COMPOSE="$DOCKER_COMPOSE -f ${1?}"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "$1" = "--use-existing" ] ; then
|
||||
shift;
|
||||
else
|
||||
@@ -13,6 +19,13 @@ else
|
||||
${DOCKER_COMPOSE?} rm -v --force
|
||||
fi
|
||||
|
||||
NO_RM=false
|
||||
|
||||
if [ "$1" = "--no-rm" ] ; then
|
||||
NO_RM=true
|
||||
shift
|
||||
fi
|
||||
|
||||
${DOCKER_COMPOSE?} up -d mongodb
|
||||
|
||||
if [ -z "$DOCKER_HOST_IP" ] ; then
|
||||
@@ -47,5 +60,7 @@ set -e
|
||||
|
||||
./gradlew -a $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false
|
||||
|
||||
${DOCKER_COMPOSE?} stop
|
||||
${DOCKER_COMPOSE?} rm -v --force
|
||||
if [ $NO_RM = false ] ; then
|
||||
${DOCKER_COMPOSE?} stop
|
||||
${DOCKER_COMPOSE?} rm -v --force
|
||||
fi
|
||||
|
||||
@@ -7,19 +7,19 @@ This application consists of three microservices:
|
||||
* Account Service - the command side business logic for Accounts
|
||||
* Money Transfer Service - the command side business logic for Money Transfers
|
||||
* Query service - query side implementation of a MongoDB-based, denormalized view of Accounts and MoneyTransfers
|
||||
|
||||
|
||||
The Account Service consists of the following modules:
|
||||
|
||||
* accounts-command-side-backend - the Account aggregate
|
||||
* accounts-command-side-web - a REST API for creating and retrieving Accounts
|
||||
* accounts-command-side-service - a standalone microservice
|
||||
|
||||
|
||||
The Money Transfer Service consists of the following modules:
|
||||
|
||||
* transactions-command-side-backend - the MoneyTransfer aggregate
|
||||
* transactions-command-side-web - a REST API for creating and retrieving Money Transfers
|
||||
* transactions-command-side-service - a standalone microservice
|
||||
|
||||
|
||||
The Query Service consists the following modules:
|
||||
|
||||
* accounts-query-side-backend - MongoDB-based, denormalized view of Accounts and MoneyTransfers
|
||||
@@ -28,10 +28,8 @@ The Query Service consists the following modules:
|
||||
|
||||
# Deploying the application
|
||||
|
||||
These services can be deployed either as either separate standalone services using the Event Store server, or they can be deployed as a monolithic application for simpified integration testing.
|
||||
These services can be deployed either as either separate standalone services using the Event Store server, or they can be deployed as a monolithic application for simplified integration testing.
|
||||
|
||||
The three services can also be packaged as a single monolithic web application in order to be used with the embedded Event Store:
|
||||
|
||||
* monolithic-service - all-in-one, monolithic packaging of the application
|
||||
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ apply plugin: 'java'
|
||||
dependencies {
|
||||
|
||||
compile project(":common-backend")
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
|
||||
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
|
||||
|
||||
testCompile project(":testutil")
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import net.chrisrichardson.eventstore.EventUtil;
|
||||
import net.chrisrichardson.eventstore.ReflectiveMutableCommandProcessingAggregate;
|
||||
import io.eventuate.Event;
|
||||
import io.eventuate.EventUtil;
|
||||
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitFailedDueToInsufficientFundsEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
|
||||
@@ -16,7 +16,7 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
|
||||
private BigDecimal balance;
|
||||
|
||||
public List<Event> process(OpenAccountCommand cmd) {
|
||||
return EventUtil.events(new AccountOpenedEvent(cmd.getInitialBalance()));
|
||||
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription()));
|
||||
}
|
||||
|
||||
public List<Event> process(DebitAccountCommand cmd) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Command;
|
||||
|
||||
import io.eventuate.Command;
|
||||
|
||||
interface AccountCommand extends Command {
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.EventStore;
|
||||
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
|
||||
import net.chrisrichardson.eventstore.repository.AggregateRepository;
|
||||
import io.eventuate.AggregateRepository;
|
||||
import io.eventuate.EventuateAggregateStore;
|
||||
import io.eventuate.javaclient.spring.EnableEventHandlers;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@EnableJavaEventHandlers
|
||||
@EnableEventHandlers
|
||||
public class AccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -22,7 +22,7 @@ public class AccountConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AggregateRepository<Account, AccountCommand> accountRepository(EventStore eventStore) {
|
||||
public AggregateRepository<Account, AccountCommand> accountRepository(EventuateAggregateStore eventStore) {
|
||||
return new AggregateRepository<Account, AccountCommand>(Account.class, eventStore);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
|
||||
import net.chrisrichardson.eventstore.repository.AggregateRepository;
|
||||
import io.eventuate.AggregateRepository;
|
||||
import io.eventuate.EntityWithIdAndVersion;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class AccountService {
|
||||
|
||||
@@ -14,8 +15,8 @@ public class AccountService {
|
||||
this.accountRepository = accountRepository;
|
||||
}
|
||||
|
||||
public rx.Observable<EntityWithIdAndVersion<Account>> openAccount(BigDecimal initialBalance) {
|
||||
return accountRepository.save(new OpenAccountCommand(initialBalance));
|
||||
public CompletableFuture<EntityWithIdAndVersion<Account>> openAccount(String customerId, String title, BigDecimal initialBalance, String description) {
|
||||
return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance, description));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.javaapi.consumer.EventHandlerContext;
|
||||
|
||||
import io.eventuate.EntityWithIdAndVersion;
|
||||
import io.eventuate.EventHandlerContext;
|
||||
import io.eventuate.EventHandlerMethod;
|
||||
import io.eventuate.EventSubscriber;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
|
||||
import net.chrisrichardson.eventstore.subscriptions.*;
|
||||
import rx.Observable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@EventSubscriber(id="accountEventHandlers")
|
||||
public class AccountWorkflow implements CompoundEventHandler {
|
||||
@EventSubscriber(id = "accountEventHandlers")
|
||||
public class AccountWorkflow {
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) {
|
||||
public CompletableFuture<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) {
|
||||
MoneyTransferCreatedEvent event = ctx.getEvent();
|
||||
BigDecimal amount = event.getDetails().getAmount();
|
||||
EntityIdentifier transactionId = ctx.getEntityIdentifier();
|
||||
String transactionId = ctx.getEntityId();
|
||||
|
||||
EntityIdentifier fromAccountId = event.getDetails().getFromAccountId();
|
||||
String fromAccountId = event.getDetails().getFromAccountId();
|
||||
|
||||
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId));
|
||||
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId)).handle((x, e) -> {
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return x;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<?> creditAccount(EventHandlerContext<DebitRecordedEvent> ctx) {
|
||||
public CompletableFuture<EntityWithIdAndVersion<Account>> creditAccount(EventHandlerContext<DebitRecordedEvent> ctx) {
|
||||
DebitRecordedEvent event = ctx.getEvent();
|
||||
BigDecimal amount = event.getDetails().getAmount();
|
||||
EntityIdentifier fromAccountId = event.getDetails().getToAccountId();
|
||||
EntityIdentifier transactionId = ctx.getEntityIdentifier();
|
||||
String fromAccountId = event.getDetails().getToAccountId();
|
||||
String transactionId = ctx.getEntityId();
|
||||
|
||||
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId));
|
||||
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId)).handle((x, e) -> {
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return x;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import io.eventuate.Aggregate;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class CreditAccountCommand implements AccountCommand {
|
||||
private final BigDecimal amount;
|
||||
private final EntityIdentifier transactionId;
|
||||
private final String transactionId;
|
||||
|
||||
public CreditAccountCommand(BigDecimal amount, EntityIdentifier transactionId) {
|
||||
public CreditAccountCommand(BigDecimal amount, String transactionId) {
|
||||
|
||||
this.amount = amount;
|
||||
this.transactionId = transactionId;
|
||||
@@ -19,7 +19,7 @@ public class CreditAccountCommand implements AccountCommand {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public EntityIdentifier getTransactionId() {
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class DebitAccountCommand implements AccountCommand {
|
||||
private final BigDecimal amount;
|
||||
private final EntityIdentifier transactionId;
|
||||
private final String transactionId;
|
||||
|
||||
public DebitAccountCommand(BigDecimal amount, EntityIdentifier transactionId) {
|
||||
public DebitAccountCommand(BigDecimal amount, String transactionId) {
|
||||
|
||||
this.amount = amount;
|
||||
this.transactionId = transactionId;
|
||||
@@ -19,7 +16,7 @@ public class DebitAccountCommand implements AccountCommand {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public EntityIdentifier getTransactionId() {
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,31 @@ import java.math.BigDecimal;
|
||||
|
||||
public class OpenAccountCommand implements AccountCommand {
|
||||
|
||||
private String customerId;
|
||||
private String title;
|
||||
private BigDecimal initialBalance;
|
||||
private String description;
|
||||
|
||||
public OpenAccountCommand(BigDecimal initialBalance) {
|
||||
public OpenAccountCommand(String customerId, String title, BigDecimal initialBalance, String description) {
|
||||
this.customerId = customerId;
|
||||
this.title = title;
|
||||
this.initialBalance = initialBalance;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public BigDecimal getInitialBalance() {
|
||||
return initialBalance;
|
||||
}
|
||||
|
||||
public String getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.CommandProcessingAggregates;
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@@ -14,14 +13,16 @@ public class AccountTest {
|
||||
@Test
|
||||
public void testSomething() {
|
||||
Account account = new Account();
|
||||
String title = "My Account";
|
||||
String customerId = "00000000-00000000";
|
||||
BigDecimal initialBalance = new BigDecimal(512);
|
||||
List<Event> events = CommandProcessingAggregates.processToList(account, (AccountCommand)new OpenAccountCommand(initialBalance));
|
||||
|
||||
List<Event> events = account.process(new OpenAccountCommand(customerId, title, initialBalance, ""));
|
||||
|
||||
Assert.assertEquals(1, events.size());
|
||||
Assert.assertEquals(AccountOpenedEvent.class, events.get(0).getClass());
|
||||
|
||||
account.applyEvent(events.get(0));
|
||||
|
||||
Assert.assertEquals(initialBalance, account.getBalance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
apply plugin: VerifyMongoDBConfigurationPlugin
|
||||
apply plugin: VerifyEventStoreEnvironmentPlugin
|
||||
apply plugin: EventuateDependencyPlugin
|
||||
|
||||
apply plugin: 'spring-boot'
|
||||
|
||||
@@ -10,9 +11,6 @@ dependencies {
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
compile "org.springframework.boot:spring-boot-starter-actuator"
|
||||
|
||||
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web;
|
||||
|
||||
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
|
||||
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CommandSideWebAccountsConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -13,7 +13,7 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
|
||||
@Configuration
|
||||
@Import({CommandSideWebAccountsConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
|
||||
@Import({CommandSideWebAccountsConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
public class AccountsCommandSideServiceConfiguration {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountRequest;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountResponse;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -25,7 +25,7 @@ public class AccountsCommandSideServiceIntegrationTest {
|
||||
private int port;
|
||||
|
||||
private String baseUrl(String path) {
|
||||
return "http://localhost:" + port + "/" + path;
|
||||
return "http://localhost:" + port + "/api" + path;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@@ -37,11 +37,13 @@ public class AccountsCommandSideServiceIntegrationTest {
|
||||
BigDecimal initialFromAccountBalance = new BigDecimal(500);
|
||||
BigDecimal initialToAccountBalance = new BigDecimal(100);
|
||||
BigDecimal amountToTransfer = new BigDecimal(150);
|
||||
String customerId = "00000000-00000000";
|
||||
String title = "My Account";
|
||||
|
||||
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialFromAccountBalance), CreateAccountResponse.class).getBody();
|
||||
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, "", initialFromAccountBalance), CreateAccountResponse.class).getBody();
|
||||
final String fromAccountId = fromAccount.getAccountId();
|
||||
|
||||
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialToAccountBalance), CreateAccountResponse.class).getBody();
|
||||
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, "", initialToAccountBalance), CreateAccountResponse.class).getBody();
|
||||
String toAccountId = toAccount.getAccountId();
|
||||
|
||||
Assert.assertNotNull(fromAccountId);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
|
||||
dependencies {
|
||||
compile project(":accounts-command-side-backend")
|
||||
compile project(":common-web")
|
||||
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import rx.Observable;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/accounts")
|
||||
@RequestMapping("/api/accounts")
|
||||
public class AccountController {
|
||||
|
||||
private AccountService accountService;
|
||||
@@ -21,8 +24,8 @@ public class AccountController {
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST)
|
||||
public Observable<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
|
||||
return accountService.openAccount(request.getInitialBalance())
|
||||
.map(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityIdentifier().getId()));
|
||||
public CompletableFuture<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
|
||||
return accountService.openAccount(request.getCustomerId(), request.getTitle(), request.getInitialBalance(), request.getDescription())
|
||||
.thenApply(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityId()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,16 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
|
||||
@Configuration
|
||||
@Import({AccountConfiguration.class})
|
||||
@ComponentScan
|
||||
public class CommandSideWebAccountsConfiguration extends WebMvcConfigurerAdapter {
|
||||
@EnableAutoConfiguration
|
||||
public class CommandSideWebAccountsConfiguration {
|
||||
|
||||
class FakeThing {}
|
||||
|
||||
@Bean
|
||||
public FakeThing init(RequestMappingHandlerAdapter adapter) {
|
||||
// https://jira.spring.io/browse/SPR-13083
|
||||
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
|
||||
handlers.add(0, new ObservableReturnValueHandler());
|
||||
adapter.setReturnValueHandlers(handlers);
|
||||
return new FakeThing();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class CreateAccountRequest {
|
||||
|
||||
@NotNull
|
||||
@DecimalMin("0")
|
||||
private BigDecimal initialBalance;
|
||||
|
||||
public CreateAccountRequest() {
|
||||
}
|
||||
|
||||
public CreateAccountRequest(BigDecimal initialBalance) {
|
||||
|
||||
this.initialBalance = initialBalance;
|
||||
}
|
||||
|
||||
public BigDecimal getInitialBalance() {
|
||||
return initialBalance;
|
||||
}
|
||||
|
||||
public void setInitialBalance(BigDecimal initialBalance) {
|
||||
this.initialBalance = initialBalance;
|
||||
}
|
||||
}
|
||||
@@ -35,16 +35,16 @@ public class AccountControllerIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void shouldCreateAccount() throws Exception {
|
||||
mockMvc.perform(post("/accounts")
|
||||
mockMvc.perform(post("/api/accounts")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"initialBalance\" : 500}")
|
||||
.content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectBadRequest() throws Exception {
|
||||
mockMvc.perform(post("/accounts")
|
||||
mockMvc.perform(post("/api/accounts")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"initialBalanceXXX\" : 500}")
|
||||
.accept(MediaType.APPLICATION_JSON))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.jdbc.config.JdbcEventStoreConfiguration;
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Import({CommandSideWebAccountsConfiguration.class, JdbcEventStoreConfiguration.class})
|
||||
@Import({CommandSideWebAccountsConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
|
||||
public class AccountControllerIntegrationTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -3,17 +3,13 @@ apply plugin: 'java'
|
||||
dependencies {
|
||||
compile project(":common-backend")
|
||||
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
|
||||
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
|
||||
|
||||
compile 'com.fasterxml.jackson.core:jackson-core:2.4.3'
|
||||
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.3'
|
||||
compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:2.4.3'
|
||||
|
||||
testCompile project(":testutil")
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
public class AccountChangeInfo {
|
||||
|
||||
private String changeId;
|
||||
private String transactionId;
|
||||
private String transactionType;
|
||||
private long amount;
|
||||
private long balanceDelta;
|
||||
|
||||
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
|
||||
this.changeId = changeId;
|
||||
this.transactionId = transactionId;
|
||||
this.transactionType = transactionType;
|
||||
this.amount = amount;
|
||||
this.balanceDelta = balanceDelta;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import java.util.List;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Created by cer on 11/21/14.
|
||||
@@ -8,40 +12,77 @@ import java.util.List;
|
||||
public class AccountInfo {
|
||||
|
||||
private String id;
|
||||
private String customerId;
|
||||
private String title;
|
||||
private String description;
|
||||
private long balance;
|
||||
private List<AccountChangeInfo> changes;
|
||||
private List<AccountTransactionInfo> transactions;
|
||||
private Map<String, AccountTransactionInfo> transactions;
|
||||
private Map<String, TransferState> transferStates;
|
||||
private String version;
|
||||
private Date date;
|
||||
|
||||
private AccountInfo() {
|
||||
}
|
||||
|
||||
public AccountInfo(String id, long balance, List<AccountChangeInfo> changes, List<AccountTransactionInfo> transactions, String version) {
|
||||
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version) {
|
||||
this(id, customerId, title, description, balance, changes, transactions, version, new Date());
|
||||
}
|
||||
|
||||
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version, Date date) {
|
||||
|
||||
this.id = id;
|
||||
this.customerId = customerId;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.balance = balance;
|
||||
this.changes = changes;
|
||||
this.transactions = transactions;
|
||||
this.version = version;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public long getBalance() {
|
||||
return balance;
|
||||
}
|
||||
|
||||
public List<AccountChangeInfo> getChanges() {
|
||||
return changes;
|
||||
return changes == null ? Collections.EMPTY_LIST : changes;
|
||||
}
|
||||
|
||||
public List<AccountTransactionInfo> getTransactions() {
|
||||
return transactions;
|
||||
return transactions == null ? Collections.EMPTY_LIST : new ArrayList<>(transactions.values());
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public Map<String, TransferState> getTransferStates() {
|
||||
return transferStates;
|
||||
}
|
||||
|
||||
public void setTransferStates(Map<String, TransferState> transferStates) {
|
||||
this.transferStates = transferStates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,9 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.ac
|
||||
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
interface AccountInfoRepository extends MongoRepository<AccountInfo, String> {
|
||||
|
||||
List<AccountInfo> findByCustomerId(String customerId);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import com.mongodb.WriteResult;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.where;
|
||||
|
||||
public class AccountInfoUpdateService {
|
||||
public class AccountInfoUpdateService {
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private AccountInfoRepository accountInfoRepository;
|
||||
@@ -25,29 +29,35 @@ public class AccountInfoUpdateService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void create(String accountId, BigDecimal initialBalance, String version) {
|
||||
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String description, String version) {
|
||||
try {
|
||||
accountInfoRepository.save(new AccountInfo(
|
||||
accountId,
|
||||
toIntegerRepr(initialBalance),
|
||||
Collections.<AccountChangeInfo>emptyList(),
|
||||
Collections.<AccountTransactionInfo>emptyList(),
|
||||
version));
|
||||
AccountChangeInfo ci = new AccountChangeInfo();
|
||||
ci.setAmount(toIntegerRepr(initialBalance));
|
||||
WriteResult x = mongoTemplate.upsert(new Query(where("id").is(accountId).and("version").exists(false)),
|
||||
new Update()
|
||||
.set("customerId", customerId)
|
||||
.set("title", title)
|
||||
.set("description", description)
|
||||
.set("balance", toIntegerRepr(initialBalance))
|
||||
.push("changes", ci)
|
||||
.set("date", new Date())
|
||||
.set("version", version),
|
||||
AccountInfo.class);
|
||||
logger.info("Saved in mongo");
|
||||
|
||||
} catch (DuplicateKeyException t) {
|
||||
logger.warn("When saving ", t);
|
||||
} catch (Throwable t) {
|
||||
logger.error("Error during saving: ");
|
||||
logger.error("Error during saving: ", t);
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void addTransaction(String eventId, String fromAccountId, AccountTransactionInfo ti) {
|
||||
mongoTemplate.updateMulti(new Query(where("id").is(fromAccountId).and("version").lt(eventId)),
|
||||
public void addTransaction(String accountId, AccountTransactionInfo ti) {
|
||||
mongoTemplate.upsert(new Query(where("id").is(accountId)),
|
||||
new Update().
|
||||
push("transactions", ti).
|
||||
set("version", eventId),
|
||||
set("transactions." + ti.getTransactionId(), ti),
|
||||
AccountInfo.class);
|
||||
}
|
||||
|
||||
@@ -61,5 +71,10 @@ public class AccountInfoUpdateService {
|
||||
AccountInfo.class);
|
||||
}
|
||||
|
||||
|
||||
public void updateTransactionStatus(String accountId, String transactionId, TransferState status) {
|
||||
mongoTemplate.upsert(new Query(where("id").is(accountId)),
|
||||
new Update().
|
||||
set("transferStates." + transactionId, status),
|
||||
AccountInfo.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.EntityNotFoundException;
|
||||
import rx.Observable;
|
||||
import java.util.List;
|
||||
|
||||
public class AccountQueryService {
|
||||
|
||||
@@ -13,11 +10,17 @@ public class AccountQueryService {
|
||||
this.accountInfoRepository = accountInfoRepository;
|
||||
}
|
||||
|
||||
public Observable<AccountInfo> findByAccountId(EntityIdentifier accountId) {
|
||||
AccountInfo account = accountInfoRepository.findOne(accountId.getId());
|
||||
public AccountInfo findByAccountId(String accountId) {
|
||||
AccountInfo account = accountInfoRepository.findOne(accountId);
|
||||
if (account == null)
|
||||
return Observable.error(new AccountNotFoundException(accountId.getId()));
|
||||
throw new AccountNotFoundException(accountId);
|
||||
else
|
||||
return Observable.just(account);
|
||||
if(account.getTransferStates()!=null)
|
||||
account.getTransactions().stream().forEach(ati -> ati.setStatus(account.getTransferStates().get(ati.getTransactionId())));
|
||||
return account;
|
||||
}
|
||||
|
||||
public List<AccountInfo> findByCustomerId(String customerId) {
|
||||
return accountInfoRepository.findByCustomerId(customerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountChangedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
|
||||
import io.eventuate.DispatchedEvent;
|
||||
import io.eventuate.EventHandlerMethod;
|
||||
import io.eventuate.EventSubscriber;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.CreditRecordedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.FailedDebitRecordedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
|
||||
import net.chrisrichardson.eventstore.subscriptions.CompoundEventHandler;
|
||||
import net.chrisrichardson.eventstore.subscriptions.DispatchedEvent;
|
||||
import net.chrisrichardson.eventstore.subscriptions.EventHandlerMethod;
|
||||
import net.chrisrichardson.eventstore.subscriptions.EventSubscriber;
|
||||
import rx.Observable;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -19,9 +18,9 @@ import java.math.BigDecimal;
|
||||
|
||||
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
|
||||
|
||||
@EventSubscriber(id="querySideEventHandlers")
|
||||
public class AccountQueryWorkflow implements CompoundEventHandler {
|
||||
|
||||
@EventSubscriber(id="querySideEventHandlers")
|
||||
public class AccountQueryWorkflow {
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private AccountInfoUpdateService accountInfoUpdateService;
|
||||
@@ -31,59 +30,76 @@ public class AccountQueryWorkflow implements CompoundEventHandler {
|
||||
}
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<Object> create(DispatchedEvent<AccountOpenedEvent> de) {
|
||||
AccountOpenedEvent event = de.event();
|
||||
String id = de.getEntityIdentifier().getId();
|
||||
String eventId = de.eventId().asString();
|
||||
public void create(DispatchedEvent<AccountOpenedEvent> de) {
|
||||
AccountOpenedEvent event = de.getEvent();
|
||||
String id = de.getEntityId();
|
||||
String eventId = de.getEventId().asString();
|
||||
logger.info("**************** account version=" + id + ", " + eventId);
|
||||
BigDecimal initialBalance = event.getInitialBalance();
|
||||
accountInfoUpdateService.create(id, initialBalance, eventId);
|
||||
return Observable.just(null);
|
||||
|
||||
String customerId = event.getCustomerId();
|
||||
String title = event.getTitle();
|
||||
String description = event.getDescription();
|
||||
accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId);
|
||||
}
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<Object> recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
|
||||
String eventId = de.eventId().asString();
|
||||
String moneyTransferId = de.getEntityIdentifier().getId();
|
||||
String fromAccountId = de.event().getDetails().getFromAccountId().getId();
|
||||
String toAccountId = de.event().getDetails().getToAccountId().getId();
|
||||
logger.info("**************** account version=" + fromAccountId + ", " + de.eventId().asString());
|
||||
logger.info("**************** account version=" + toAccountId + ", " + de.eventId().asString());
|
||||
public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
|
||||
String eventId = de.getEventId().asString();
|
||||
String moneyTransferId = de.getEntityId();
|
||||
String fromAccountId = de.getEvent().getDetails().getFromAccountId();
|
||||
String toAccountId = de.getEvent().getDetails().getToAccountId();
|
||||
logger.info("**************** account version=" + fromAccountId + ", " + de.getEventId().asString());
|
||||
logger.info("**************** account version=" + toAccountId + ", " + de.getEventId().asString());
|
||||
|
||||
AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId, fromAccountId, toAccountId, toIntegerRepr(de.event().getDetails().getAmount()));
|
||||
|
||||
|
||||
accountInfoUpdateService.addTransaction(eventId, fromAccountId, ti);
|
||||
accountInfoUpdateService.addTransaction(eventId, toAccountId, ti);
|
||||
|
||||
return Observable.just(null);
|
||||
AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId,
|
||||
fromAccountId,
|
||||
toAccountId,
|
||||
toIntegerRepr(de.getEvent().getDetails().getAmount()),
|
||||
de.getEvent().getDetails().getDate(),
|
||||
de.getEvent().getDetails().getDescription());
|
||||
|
||||
accountInfoUpdateService.addTransaction(fromAccountId, ti);
|
||||
accountInfoUpdateService.addTransaction(toAccountId, ti);
|
||||
}
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<Object> recordDebit(DispatchedEvent<AccountDebitedEvent> de) {
|
||||
return saveChange(de, -1);
|
||||
public void recordDebit(DispatchedEvent<AccountDebitedEvent> de) {
|
||||
String accountId = de.getEntityId();
|
||||
String transactionId = de.getEvent().getTransactionId();
|
||||
|
||||
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.DEBITED);
|
||||
saveChange(de, -1);
|
||||
}
|
||||
|
||||
@EventHandlerMethod
|
||||
public Observable<Object> recordCredit(DispatchedEvent<AccountCreditedEvent> de) {
|
||||
return saveChange(de, +1);
|
||||
public void recordCredit(DispatchedEvent<AccountCreditedEvent> de) {
|
||||
String accountId = de.getEntityId();
|
||||
String transactionId = de.getEvent().getTransactionId();
|
||||
|
||||
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
|
||||
saveChange(de, +1);
|
||||
}
|
||||
|
||||
public <T extends AccountChangedEvent> Observable<Object> saveChange(DispatchedEvent<T> de, int delta) {
|
||||
String changeId = de.eventId().asString();
|
||||
String transactionId = de.event().getTransactionId().getId();
|
||||
long amount = toIntegerRepr(de.event().getAmount());
|
||||
@EventHandlerMethod
|
||||
public void recordFailed(DispatchedEvent<AccountDebitFailedDueToInsufficientFundsEvent> de) {
|
||||
String accountId = de.getEntityId();
|
||||
String transactionId = de.getEvent().getTransactionId();
|
||||
|
||||
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS);
|
||||
}
|
||||
|
||||
public <T extends AccountChangedEvent> void saveChange(DispatchedEvent<T> de, int delta) {
|
||||
String changeId = de.getEventId().asString();
|
||||
String transactionId = de.getEvent().getTransactionId();
|
||||
long amount = toIntegerRepr(de.getEvent().getAmount());
|
||||
|
||||
long balanceDelta = amount * delta;
|
||||
AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.event().getClass().getSimpleName(), amount, balanceDelta);
|
||||
String accountId = de.getEntityIdentifier().getId();
|
||||
logger.info("**************** account version=" + accountId + ", " + de.eventId().asString());
|
||||
AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.getEvent().getClass().getSimpleName(), amount, balanceDelta);
|
||||
String accountId = de.getEntityId();
|
||||
logger.info("**************** account version=" + accountId + ", " + de.getEventId().asString());
|
||||
|
||||
accountInfoUpdateService.updateBalance(accountId, changeId, balanceDelta, ci);
|
||||
|
||||
return Observable.just(null);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
public class AccountTransactionInfo {
|
||||
|
||||
private String transactionId;
|
||||
private String fromAccountId;
|
||||
private String toAccountId;
|
||||
private long amount;
|
||||
|
||||
public AccountTransactionInfo(String transactionId, String fromAccountId, String toAccountId, long amount) {
|
||||
this.transactionId = transactionId;
|
||||
this.fromAccountId = fromAccountId;
|
||||
this.toAccountId = toAccountId;
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
|
||||
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
|
||||
import io.eventuate.javaclient.spring.EnableEventHandlers;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
@@ -9,7 +9,7 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie
|
||||
|
||||
@Configuration
|
||||
@EnableMongoRepositories
|
||||
@EnableJavaEventHandlers
|
||||
@EnableEventHandlers
|
||||
public class QuerySideAccountConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -27,8 +27,6 @@ public class QuerySideAccountConfiguration {
|
||||
return new AccountQueryService(accountInfoRepository);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
|
||||
return new QuerySideDependencyChecker(mongoTemplate);
|
||||
|
||||
@@ -3,8 +3,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.ac
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import rx.Observable;
|
||||
import rx.Subscriber;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -22,17 +20,7 @@ public class QuerySideDependencyChecker {
|
||||
try {
|
||||
logger.info("Checking mongodb connectivity {}", System.getenv("SPRING_DATA_MONGODB_URI"));
|
||||
|
||||
Observable.<Object>create(new Observable.OnSubscribe<Object>() {
|
||||
@Override
|
||||
public void call(Subscriber<? super Object> subscriber) {
|
||||
try {
|
||||
subscriber.onNext(mongoTemplate.getDb().getCollectionNames());
|
||||
subscriber.onCompleted();
|
||||
} catch (Throwable t) {
|
||||
subscriber.onError(t);
|
||||
}
|
||||
}
|
||||
}).timeout(5, TimeUnit.SECONDS).toBlocking().first();
|
||||
mongoTemplate.getDb().getCollectionNames();
|
||||
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Error connecting to Mongo - have you set SPRING_DATA_MONGODB_URI or --spring.data.mongodb_uri?", e);
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import io.eventuate.javaclient.spring.jdbc.IdGenerator;
|
||||
import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = AccountInfoUpdateServiceTest.AccountInfoUpdateServiceTestConfiguration.class)
|
||||
@IntegrationTest
|
||||
public class AccountInfoUpdateServiceTest {
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@Import({QuerySideAccountConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
|
||||
public static class AccountInfoUpdateServiceTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private AccountInfoUpdateService accountInfoUpdateService;
|
||||
|
||||
@Autowired
|
||||
private AccountQueryService accountQueryService;
|
||||
|
||||
@Test
|
||||
public void shouldSaveAccountInfo() throws ExecutionException, InterruptedException {
|
||||
IdGenerator x = new IdGeneratorImpl();
|
||||
String accountId = x.genId().asString();
|
||||
String customerId = x.genId().asString();
|
||||
String version = x.genId().asString();
|
||||
|
||||
String title = "Checking account";
|
||||
BigDecimal initialBalance = new BigDecimal("1345");
|
||||
String description = "Some account";
|
||||
|
||||
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
|
||||
|
||||
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
|
||||
|
||||
assertEquals(accountId, accountInfo.getId());
|
||||
assertEquals(customerId, accountInfo.getCustomerId());
|
||||
assertEquals(title, accountInfo.getTitle());
|
||||
assertEquals(description, accountInfo.getDescription());
|
||||
assertEquals(initialBalance.longValue() * 100, accountInfo.getBalance());
|
||||
assertEquals(1, accountInfo.getChanges().size());
|
||||
assertTrue(accountInfo.getTransactions().isEmpty());
|
||||
assertEquals(version, accountInfo.getVersion());
|
||||
|
||||
|
||||
String changeId = x.genId().asString();
|
||||
|
||||
String transactionId = x.genId().asString();
|
||||
|
||||
AccountChangeInfo change = new AccountChangeInfo(changeId, transactionId, AccountCreditedEvent.class.getSimpleName(),
|
||||
500, +1);
|
||||
|
||||
accountInfoUpdateService.updateBalance(accountId, changeId, 500,
|
||||
change);
|
||||
|
||||
accountInfo = accountQueryService.findByAccountId(accountId);
|
||||
assertEquals(initialBalance.add(new BigDecimal(5)).longValue() * 100, accountInfo.getBalance());
|
||||
assertFalse(accountInfo.getChanges().isEmpty());
|
||||
|
||||
assertEquals(change, accountInfo.getChanges().get(1));
|
||||
|
||||
String eventId = x.genId().asString();
|
||||
|
||||
AccountTransactionInfo ti = new AccountTransactionInfo(transactionId, accountId, accountId, 5, new Date(), "A transfer");
|
||||
|
||||
accountInfoUpdateService.addTransaction(accountId, ti);
|
||||
|
||||
accountInfo = accountQueryService.findByAccountId(accountId);
|
||||
assertFalse(accountInfo.getTransactions().isEmpty());
|
||||
|
||||
assertEquals(ti, accountInfo.getTransactions().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleDuplicateSaveAccountInfo() throws ExecutionException, InterruptedException {
|
||||
IdGenerator x = new IdGeneratorImpl();
|
||||
String accountId = x.genId().asString();
|
||||
String customerId = x.genId().asString();
|
||||
String version = x.genId().asString();
|
||||
|
||||
String title = "Checking account";
|
||||
BigDecimal initialBalance = new BigDecimal("1345");
|
||||
String description = "Some account";
|
||||
|
||||
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
|
||||
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUpdateTransactionStatus() {
|
||||
IdGenerator x = new IdGeneratorImpl();
|
||||
String accountId = x.genId().asString();
|
||||
String customerId = x.genId().asString();
|
||||
String version = x.genId().asString();
|
||||
|
||||
String title = "Checking account";
|
||||
BigDecimal initialBalance = new BigDecimal("1345");
|
||||
String description = "Some account";
|
||||
|
||||
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
|
||||
|
||||
String transactionId = x.genId().asString();
|
||||
|
||||
AccountTransactionInfo transactionInfo = new AccountTransactionInfo();
|
||||
transactionInfo.setTransactionId(transactionId);
|
||||
transactionInfo.setStatus(TransferState.INITIAL);
|
||||
|
||||
accountInfoUpdateService.addTransaction(accountId, transactionInfo);
|
||||
|
||||
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
|
||||
assertEquals(accountId, accountInfo.getId());
|
||||
assertFalse(accountInfo.getTransactions().isEmpty());
|
||||
assertEquals(1, accountInfo.getTransactions().size());
|
||||
|
||||
assertEquals(TransferState.INITIAL, accountInfo.getTransactions().get(0).getStatus());
|
||||
|
||||
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
|
||||
|
||||
accountInfo = accountQueryService.findByAccountId(accountId);
|
||||
assertEquals(accountId, accountInfo.getId());
|
||||
assertFalse(accountInfo.getTransactions().isEmpty());
|
||||
assertEquals(1, accountInfo.getTransactions().size());
|
||||
|
||||
assertEquals(TransferState.COMPLETED, accountInfo.getTransactions().get(0).getStatus());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
apply plugin: VerifyMongoDBConfigurationPlugin
|
||||
apply plugin: VerifyEventStoreEnvironmentPlugin
|
||||
apply plugin: EventuateDependencyPlugin
|
||||
|
||||
apply plugin: 'spring-boot'
|
||||
|
||||
@@ -10,8 +11,6 @@ dependencies {
|
||||
compile "org.springframework.boot:spring-boot-starter-web"
|
||||
compile "org.springframework.boot:spring-boot-starter-actuator"
|
||||
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
|
||||
|
||||
testCompile project(":testutil")
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web;
|
||||
|
||||
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
|
||||
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.QuerySideWebConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -13,7 +13,7 @@ import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
|
||||
@Configuration
|
||||
@Import({QuerySideWebConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
|
||||
@Import({QuerySideWebConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
@ComponentScan
|
||||
public class AccountsQuerySideServiceConfiguration {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts.GetAccountResponse;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.GetAccountResponse;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -13,10 +13,10 @@ import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
|
||||
import rx.Observable;
|
||||
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class AccountsQuerySideServiceIntegrationTest {
|
||||
private int port;
|
||||
|
||||
private String baseUrl(String path) {
|
||||
return "http://localhost:" + port + "/" + path;
|
||||
return "http://localhost:" + port + "/api" + path;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@@ -51,8 +51,8 @@ public class AccountsQuerySideServiceIntegrationTest {
|
||||
eventually(
|
||||
new Producer<GetAccountResponse>() {
|
||||
@Override
|
||||
public Observable<GetAccountResponse> produce() {
|
||||
return Observable.just(restTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId), GetAccountResponse.class).getBody());
|
||||
public CompletableFuture<GetAccountResponse> produce() {
|
||||
return CompletableFuture.completedFuture(restTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId), GetAccountResponse.class).getBody());
|
||||
}
|
||||
},
|
||||
new Verifier<GetAccountResponse>() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
dependencies {
|
||||
compile project(":accounts-query-side-backend")
|
||||
compile project(":common-web")
|
||||
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
|
||||
@@ -1,36 +1,14 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.QuerySideAccountConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
|
||||
@Configuration
|
||||
@Import({QuerySideAccountConfiguration.class})
|
||||
@ComponentScan
|
||||
public class QuerySideWebConfiguration extends WebMvcConfigurerAdapter {
|
||||
public class QuerySideWebConfiguration {
|
||||
|
||||
class FakeThing {}
|
||||
|
||||
@Bean
|
||||
public FakeThing init(RequestMappingHandlerAdapter adapter) {
|
||||
// https://jira.spring.io/browse/SPR-13083
|
||||
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
|
||||
handlers.add(0, new ObservableReturnValueHandler());
|
||||
adapter.setReturnValueHandlers(handlers);
|
||||
return new FakeThing();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountInfo;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountNotFoundException;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountQueryService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import rx.Observable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class AccountQueryController {
|
||||
|
||||
private AccountQueryService accountInfoQueryService;
|
||||
@@ -20,17 +26,40 @@ public class AccountQueryController {
|
||||
this.accountInfoQueryService = accountInfoQueryService;
|
||||
}
|
||||
|
||||
@RequestMapping(value="/accounts/{accountId}", method = RequestMethod.GET)
|
||||
public Observable<GetAccountResponse> get(@PathVariable String accountId) {
|
||||
return accountInfoQueryService.findByAccountId(new EntityIdentifier(accountId))
|
||||
.map(accountInfo -> new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance())));
|
||||
@RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.GET)
|
||||
public ResponseEntity<GetAccountResponse> get(@PathVariable String accountId) {
|
||||
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
|
||||
return ResponseEntity.ok().body(new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription()));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/customers/{customerId}/accounts", method = RequestMethod.GET)
|
||||
public ResponseEntity<GetAccountsResponse> getAccountsForCustomer(@PathVariable("customerId") String customerId) {
|
||||
return ResponseEntity.ok().body(
|
||||
new GetAccountsResponse(
|
||||
accountInfoQueryService.findByCustomerId(customerId)
|
||||
.stream()
|
||||
.map(accountInfo -> new GetAccountResponse(
|
||||
accountInfo.getId(),
|
||||
new BigDecimal(accountInfo.getBalance()),
|
||||
accountInfo.getTitle(),
|
||||
accountInfo.getDescription()))
|
||||
.collect(Collectors.toList())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/accounts/{accountId}/history", method = RequestMethod.GET)
|
||||
public ResponseEntity<AccountHistoryResponse> getTransactionsHistory(@PathVariable String accountId) {
|
||||
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
|
||||
List<AccountHistoryEntry> historyEntries = new ArrayList<>();
|
||||
historyEntries.add(new AccountOpenInfo(accountInfo.getDate(), AccountHistoryEntry.EntryType.account, accountInfo.getChanges().get(0).getAmount()));
|
||||
accountInfo.getTransactions().forEach(historyEntries::add);
|
||||
|
||||
return ResponseEntity.ok().body(new AccountHistoryResponse(historyEntries));
|
||||
}
|
||||
|
||||
@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="account not found")
|
||||
@ExceptionHandler(AccountNotFoundException.class)
|
||||
public void accountNotFound() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
22
java-spring/api-gateway-service/build.gradle
Normal file
22
java-spring/api-gateway-service/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'spring-boot'
|
||||
apply plugin: EventuateDependencyPlugin
|
||||
|
||||
dependencies {
|
||||
compile project(":common-auth-web")
|
||||
|
||||
compile "org.apache.httpcomponents:httpclient:4.5"
|
||||
compile "org.apache.httpcomponents:fluent-hc:4.5.1"
|
||||
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
}
|
||||
|
||||
task copyWebStatic(type: Copy) {
|
||||
from "../../js-frontend/build"
|
||||
into "build/resources/main/static"
|
||||
}
|
||||
|
||||
jar.dependsOn(copyWebStatic)
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 15.01.16.
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "api.gateway")
|
||||
public class ApiGatewayProperties {
|
||||
|
||||
private List<Endpoint> endpoints;
|
||||
|
||||
public static class Endpoint {
|
||||
private String path;
|
||||
private RequestMethod method;
|
||||
private String location;
|
||||
|
||||
public Endpoint() {
|
||||
}
|
||||
|
||||
public Endpoint(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public RequestMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(RequestMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Endpoint> getEndpoints() {
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
public void setEndpoints(List<Endpoint> endpoints) {
|
||||
this.endpoints = endpoints;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 15.01.16.
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableAutoConfiguration
|
||||
@Import({AuthConfiguration.class})
|
||||
@EnableConfigurationProperties({ApiGatewayProperties.class})
|
||||
public class ApiGatewayServiceConfiguration extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate(HttpMessageConverters converters) {
|
||||
|
||||
// we have to define Apache HTTP client to use the PATCH verb
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/json"));
|
||||
converter.setObjectMapper(new ObjectMapper());
|
||||
|
||||
HttpClient httpClient = HttpClients.createDefault();
|
||||
RestTemplate restTemplate = new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(converter));
|
||||
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
|
||||
|
||||
restTemplate.setErrorHandler(new RestTemplateErrorHandler());
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RestTemplateErrorHandler implements ResponseErrorHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RestTemplateErrorHandler.class);
|
||||
|
||||
@Override
|
||||
public void handleError(ClientHttpResponse response) throws IOException {
|
||||
log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasError(ClientHttpResponse response) throws IOException {
|
||||
return RestUtil.isError(response.getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 07.12.15.
|
||||
*/
|
||||
public class RestUtil {
|
||||
|
||||
public static boolean isError(HttpStatus status) {
|
||||
HttpStatus.Series series = status.series();
|
||||
return (HttpStatus.Series.CLIENT_ERROR.equals(series)
|
||||
|| HttpStatus.Series.SERVER_ERROR.equals(series));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.controller;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayProperties;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.ContentRequestTransformer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.HeadersRequestTransformer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.URLRequestTransformer;
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 15.01.16.
|
||||
*/
|
||||
@RestController
|
||||
public class GatewayController {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private ApiGatewayProperties apiGatewayProperties;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
|
||||
|
||||
httpClient = HttpClients.custom()
|
||||
.setConnectionManager(cm)
|
||||
.build();
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/api/**", method = {GET, POST})
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException {
|
||||
HttpUriRequest proxiedRequest = createHttpUriRequest(request);
|
||||
logger.info("request: {}", proxiedRequest);
|
||||
HttpResponse proxiedResponse = httpClient.execute(proxiedRequest);
|
||||
logger.info("Response {}", proxiedResponse.getStatusLine().getStatusCode());
|
||||
return new ResponseEntity<>(read(proxiedResponse.getEntity().getContent()), processHeaders(proxiedResponse.getAllHeaders()), HttpStatus.valueOf(proxiedResponse.getStatusLine().getStatusCode()));
|
||||
}
|
||||
|
||||
private HttpHeaders processHeaders(Header[] headers) {
|
||||
HttpHeaders result = new HttpHeaders();
|
||||
Stream.of(headers).filter(h -> h.getName().equalsIgnoreCase("Content-Type")).forEach( h -> result.set(h.getName(), h.getValue()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private HttpUriRequest createHttpUriRequest(HttpServletRequest request) throws URISyntaxException, NoSuchRequestHandlingMethodException, IOException {
|
||||
URLRequestTransformer urlRequestTransformer = new URLRequestTransformer(apiGatewayProperties);
|
||||
ContentRequestTransformer contentRequestTransformer = new ContentRequestTransformer();
|
||||
HeadersRequestTransformer headersRequestTransformer = new HeadersRequestTransformer();
|
||||
headersRequestTransformer.setPredecessor(contentRequestTransformer);
|
||||
contentRequestTransformer.setPredecessor(urlRequestTransformer);
|
||||
|
||||
return headersRequestTransformer.transform(request).build();
|
||||
}
|
||||
|
||||
private String read(InputStream input) throws IOException {
|
||||
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) {
|
||||
return buffer.lines().collect(Collectors.joining("\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.main;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayServiceConfiguration;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
|
||||
/**
|
||||
* Created by Main on 19.01.2016.
|
||||
*/
|
||||
public class ApiGatewayServiceMain {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiGatewayServiceConfiguration.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
|
||||
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.01.16.
|
||||
*/
|
||||
public class ContentRequestTransformer extends ProxyRequestTransformer {
|
||||
|
||||
@Override
|
||||
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
|
||||
RequestBuilder requestBuilder = predecessor.transform(request);
|
||||
|
||||
String requestContent = request.getReader().lines().collect(Collectors.joining(""));
|
||||
if (!requestContent.isEmpty()) {
|
||||
StringEntity entity = new StringEntity(requestContent, ContentType.APPLICATION_JSON);
|
||||
requestBuilder.setEntity(entity);
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
|
||||
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.01.16.
|
||||
*/
|
||||
public class HeadersRequestTransformer extends ProxyRequestTransformer {
|
||||
|
||||
@Override
|
||||
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
|
||||
RequestBuilder requestBuilder = predecessor.transform(request);
|
||||
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
if (headerName.equals("x-access-token")) {
|
||||
requestBuilder.addHeader(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
|
||||
return requestBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
|
||||
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.01.16.
|
||||
*/
|
||||
public abstract class ProxyRequestTransformer {
|
||||
|
||||
protected ProxyRequestTransformer predecessor;
|
||||
|
||||
public abstract RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException;
|
||||
|
||||
public void setPredecessor(ProxyRequestTransformer transformer) {
|
||||
this.predecessor = transformer;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayProperties;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.01.16.
|
||||
*/
|
||||
public class URLRequestTransformer extends ProxyRequestTransformer {
|
||||
|
||||
private ApiGatewayProperties apiGatewayProperties;
|
||||
|
||||
public URLRequestTransformer(ApiGatewayProperties apiGatewayProperties) {
|
||||
this.apiGatewayProperties = apiGatewayProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException {
|
||||
String requestURI = request.getRequestURI();
|
||||
URI uri;
|
||||
if (request.getQueryString() != null && !request.getQueryString().isEmpty()) {
|
||||
uri = new URI(getServiceUrl(requestURI, request) + "?" + request.getQueryString());
|
||||
} else {
|
||||
uri = new URI(getServiceUrl(requestURI, request));
|
||||
}
|
||||
|
||||
RequestBuilder rb = RequestBuilder.create(request.getMethod());
|
||||
rb.setUri(uri);
|
||||
return rb;
|
||||
}
|
||||
|
||||
private String getServiceUrl(String requestURI, HttpServletRequest httpServletRequest) throws NoSuchRequestHandlingMethodException {
|
||||
|
||||
ApiGatewayProperties.Endpoint endpoint =
|
||||
apiGatewayProperties.getEndpoints().stream()
|
||||
.filter(e ->
|
||||
requestURI.matches(e.getPath()) && e.getMethod() == RequestMethod.valueOf(httpServletRequest.getMethod())
|
||||
)
|
||||
.findFirst().orElseThrow(() -> new NoSuchRequestHandlingMethodException(httpServletRequest));
|
||||
return endpoint.getLocation() + requestURI;
|
||||
}
|
||||
}
|
||||
25
java-spring/api-gateway-service/src/main/resources/application.properties
Executable file
25
java-spring/api-gateway-service/src/main/resources/application.properties
Executable file
@@ -0,0 +1,25 @@
|
||||
accounts.commandside.service.host=localhost
|
||||
accounts.queryside.service.host=localhost
|
||||
customers.commandside.service.host=localhost
|
||||
customers.queryside.service.host=localhost
|
||||
transfers.commandside.service.host=localhost
|
||||
|
||||
|
||||
api.gateway.endpoints[0].path=[/]*api/accounts.*
|
||||
api.gateway.endpoints[0].method=GET
|
||||
api.gateway.endpoints[0].location=http://${accounts.queryside.service.host}:8080
|
||||
api.gateway.endpoints[1].path=[/]*api/customers.*/accounts
|
||||
api.gateway.endpoints[1].method=GET
|
||||
api.gateway.endpoints[1].location=http://${accounts.queryside.service.host}:8080
|
||||
api.gateway.endpoints[2].path=[/]*api/accounts.*
|
||||
api.gateway.endpoints[2].method=POST
|
||||
api.gateway.endpoints[2].location=http://${accounts.commandside.service.host}:8080
|
||||
api.gateway.endpoints[3].path=[/]*api/customers.*
|
||||
api.gateway.endpoints[3].method=GET
|
||||
api.gateway.endpoints[3].location=http://${customers.queryside.service.host}:8080
|
||||
api.gateway.endpoints[4].path=[/]*api/customers.*
|
||||
api.gateway.endpoints[4].method=POST
|
||||
api.gateway.endpoints[4].location=http://${customers.commandside.service.host}:8080
|
||||
api.gateway.endpoints[5].path=[/]*api/transfers.*
|
||||
api.gateway.endpoints[5].method=POST
|
||||
api.gateway.endpoints[5].location=http://${transfers.commandside.service.host}:8080
|
||||
24
java-spring/api-gateway-service/src/main/resources/logback.xml
Executable file
24
java-spring/api-gateway-service/src/main/resources/logback.xml
Executable file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<!-- [%thread] -->
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="error">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
<logger name="org.springframework" level='info'>
|
||||
</logger>
|
||||
|
||||
<logger name="net.chrisrichardson.eventstore.javaexamples.banking" level='info'>
|
||||
</logger>
|
||||
|
||||
<logger name="io.eventuate" level='debug'>
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
@@ -5,9 +5,11 @@ dependencies {
|
||||
testCompile project(":accounts-command-side-backend")
|
||||
testCompile project(":transactions-command-side-backend")
|
||||
testCompile project(":accounts-query-side-backend")
|
||||
testCompile project(":customers-command-side-backend")
|
||||
testCompile project(":customers-query-side-backend")
|
||||
testCompile project(":testutil")
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend;
|
||||
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferConfiguration;
|
||||
import net.chrisrichardson.eventstore.jdbc.config.JdbcEventStoreConfiguration;
|
||||
import net.chrisrichardson.utils.config.MetricRegistryConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
|
||||
@Configuration
|
||||
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, JdbcEventStoreConfiguration.class})
|
||||
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
public class BankingTestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend;
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
|
||||
import net.chrisrichardson.eventstore.EntityWithMetadata;
|
||||
import net.chrisrichardson.eventstore.EventStore;
|
||||
import io.eventuate.EntityWithIdAndVersion;
|
||||
import io.eventuate.EventuateAggregateStore;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -16,18 +15,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import rx.Observable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
|
||||
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes=BankingTestConfiguration.class)
|
||||
@SpringApplicationConfiguration(classes = BankingTestConfiguration.class)
|
||||
@IntegrationTest
|
||||
public class MoneyTransferIntegrationTest {
|
||||
|
||||
@@ -38,117 +34,57 @@ public class MoneyTransferIntegrationTest {
|
||||
private MoneyTransferService moneyTransferService;
|
||||
|
||||
@Autowired
|
||||
private EventStore eventStore;
|
||||
|
||||
private EventuateAggregateStore eventStore;
|
||||
|
||||
|
||||
@Test
|
||||
public void shouldTransferMoney() {
|
||||
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
|
||||
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
|
||||
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
|
||||
|
||||
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
|
||||
toAccount.getEntityIdentifier(),
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
|
||||
toAccount.getEntityId(),
|
||||
new BigDecimal(80))));
|
||||
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<Account>> produce() {
|
||||
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, fromAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<Account> account) {
|
||||
Assert.assertEquals(new BigDecimal(70), account.entity().getBalance());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(Account.class, fromAccount.getEntityId()),
|
||||
account -> Assert.assertEquals(new BigDecimal(70), account.getEntity().getBalance()));
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<Account>> produce() {
|
||||
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, toAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<Account> account) {
|
||||
Assert.assertEquals(new BigDecimal(380), account.entity().getBalance());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(Account.class, toAccount.getEntityId()),
|
||||
account -> Assert.assertEquals(new BigDecimal(380), account.getEntity().getBalance()));
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
|
||||
return (Observable<EntityWithMetadata<MoneyTransfer>>)eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
|
||||
Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.entity().getState());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
|
||||
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailDueToInsufficientFunds() {
|
||||
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
|
||||
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
|
||||
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
|
||||
|
||||
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
|
||||
toAccount.getEntityIdentifier(),
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
|
||||
toAccount.getEntityId(),
|
||||
new BigDecimal(200))));
|
||||
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
|
||||
return (Observable<EntityWithMetadata<MoneyTransfer>>)eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
|
||||
Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.entity().getState());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
|
||||
updatedTransaction -> Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.getEntity().getState()));
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<Account>> produce() {
|
||||
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, fromAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<Account> account) {
|
||||
Assert.assertEquals(new BigDecimal(150), account.entity().getBalance());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(Account.class, fromAccount.getEntityId()),
|
||||
account -> Assert.assertEquals(new BigDecimal(150), account.getEntity().getBalance()));
|
||||
|
||||
eventually (
|
||||
new Producer<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<Account>> produce() {
|
||||
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, toAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<Account>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<Account> account) {
|
||||
Assert.assertEquals(new BigDecimal(300), account.entity().getBalance());
|
||||
}
|
||||
});
|
||||
eventually(
|
||||
() -> eventStore.find(Account.class, toAccount.getEntityId()),
|
||||
account -> Assert.assertEquals(new BigDecimal(300), account.getEntity().getBalance()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
|
||||
import net.chrisrichardson.eventstore.EntityWithMetadata;
|
||||
import net.chrisrichardson.eventstore.EventStore;
|
||||
import io.eventuate.EntityWithIdAndVersion;
|
||||
import io.eventuate.EventuateAggregateStore;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -18,9 +15,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import rx.Observable;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
|
||||
@@ -37,7 +34,7 @@ public class AccountQuerySideIntegrationTest {
|
||||
private MoneyTransferService moneyTransferService;
|
||||
|
||||
@Autowired
|
||||
private EventStore eventStore;
|
||||
private EventuateAggregateStore eventStore;
|
||||
|
||||
@Autowired
|
||||
private AccountQueryService accountQueryService;
|
||||
@@ -45,54 +42,24 @@ public class AccountQuerySideIntegrationTest {
|
||||
@Test
|
||||
public void shouldUpdateQuerySide() throws Exception {
|
||||
|
||||
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount(new BigDecimal(150)));
|
||||
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
|
||||
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
|
||||
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
|
||||
|
||||
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
|
||||
toAccount.getEntityIdentifier(),
|
||||
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
|
||||
toAccount.getEntityId(),
|
||||
new BigDecimal(80))));
|
||||
|
||||
eventually(
|
||||
new Producer<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
|
||||
return eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
|
||||
@Override
|
||||
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
|
||||
Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.entity().getState());
|
||||
}
|
||||
});
|
||||
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
|
||||
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
|
||||
|
||||
eventually(
|
||||
new Producer<AccountInfo>() {
|
||||
@Override
|
||||
public Observable<AccountInfo> produce() {
|
||||
return accountQueryService.findByAccountId(fromAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<AccountInfo>() {
|
||||
@Override
|
||||
public void verify(AccountInfo accountInfo) {
|
||||
Assert.assertEquals(70*100, accountInfo.getBalance());
|
||||
}
|
||||
});
|
||||
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(fromAccount.getEntityId())),
|
||||
accountInfo -> Assert.assertEquals(70 * 100, accountInfo.getBalance()));
|
||||
eventually(
|
||||
new Producer<AccountInfo>() {
|
||||
@Override
|
||||
public Observable<AccountInfo> produce() {
|
||||
return accountQueryService.findByAccountId(toAccount.getEntityIdentifier());
|
||||
}
|
||||
},
|
||||
new Verifier<AccountInfo>() {
|
||||
@Override
|
||||
public void verify(AccountInfo accountInfo) {
|
||||
Assert.assertEquals(380*100, accountInfo.getBalance());
|
||||
}
|
||||
});
|
||||
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(toAccount.getEntityId())),
|
||||
accountInfo -> Assert.assertEquals(380 * 100, accountInfo.getBalance()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
|
||||
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferConfiguration;
|
||||
import net.chrisrichardson.eventstore.jdbc.config.JdbcEventStoreConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, JdbcEventStoreConfiguration.class, QuerySideAccountConfiguration.class})
|
||||
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, EventuateJdbcEventStoreConfiguration.class,
|
||||
QuerySideAccountConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
public class AccountQuerySideTestConfiguration {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
|
||||
|
||||
import io.eventuate.EntityWithIdAndVersion;
|
||||
import io.eventuate.EventuateAggregateStore;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
|
||||
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.IntegrationTest;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateCustomerInfo;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateToAccountInfo;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
|
||||
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
|
||||
|
||||
/**
|
||||
* Created by Main on 10.02.2016.
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = CustomerQuerySideTestConfiguration.class)
|
||||
@IntegrationTest
|
||||
public class CustomerQuerySideIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private CustomerService customerService;
|
||||
|
||||
@Autowired
|
||||
private CustomerQueryService customerQueryService;
|
||||
|
||||
@Autowired
|
||||
private EventuateAggregateStore eventStore;
|
||||
|
||||
@Test
|
||||
public void shouldCreateCustomerAndAddToAccount() throws Exception {
|
||||
CustomerInfo customerInfo = generateCustomerInfo();
|
||||
EntityWithIdAndVersion<Customer> customer = await(customerService.createCustomer(customerInfo));
|
||||
|
||||
ToAccountInfo toAccountInfo = generateToAccountInfo();
|
||||
EntityWithIdAndVersion<Customer> customerWithNewAccount = await(customerService.addToAccount(customer.getEntityId(), toAccountInfo));
|
||||
|
||||
eventually(
|
||||
new Producer<QuerySideCustomer>() {
|
||||
@Override
|
||||
public CompletableFuture<QuerySideCustomer> produce() {
|
||||
return customerQueryService.findByCustomerId(customer.getEntityId());
|
||||
}
|
||||
},
|
||||
new Verifier<QuerySideCustomer>() {
|
||||
@Override
|
||||
public void verify(QuerySideCustomer querySideCustomer) {
|
||||
Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName());
|
||||
Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
|
||||
Assert.assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail());
|
||||
Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
|
||||
Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
|
||||
|
||||
Assert.assertNotNull(querySideCustomer.getToAccounts());
|
||||
Assert.assertFalse(querySideCustomer.getToAccounts().isEmpty());
|
||||
Assert.assertEquals(querySideCustomer.getToAccounts().get("11111111-11111111"), toAccountInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
|
||||
|
||||
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerConfiguration;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Import({CustomerConfiguration.class, EventuateJdbcEventStoreConfiguration.class, QuerySideCustomerConfiguration.class})
|
||||
@EnableAutoConfiguration
|
||||
public class CustomerQuerySideTestConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
|
||||
class EventuateDependencyPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
project.dependencies {
|
||||
if (project.hasProperty("eventuateLocal")) {
|
||||
compile "io.eventuate.local.java:eventuate-local-java-jdbc:${project.eventuateLocalVersion}"
|
||||
compile "io.eventuate.local.java:eventuate-local-java-embedded-cdc-autoconfigure:${project.eventuateLocalVersion}"
|
||||
} else
|
||||
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:${project.eventuateClientVersion}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
|
||||
compile project(":common-auth")
|
||||
compile project(":common")
|
||||
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.CustomerAuthService;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.AuthRequest;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.ErrorResponse;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.token.Token;
|
||||
import org.springframework.security.core.token.TokenService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.09.15.
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@RequestMapping("/api")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
private CustomerAuthService customerAuthService;
|
||||
|
||||
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@RequestMapping(value = "/login", method = POST)
|
||||
public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid AuthRequest request) throws IOException {
|
||||
QuerySideCustomer customer = customerAuthService.findByEmail(request.getEmail());
|
||||
|
||||
Token token = tokenService.allocateToken(objectMapper.writeValueAsString(new User(request.getEmail())));
|
||||
return ResponseEntity.status(HttpStatus.OK).header("access-token", token.getKey())
|
||||
.body(customer);
|
||||
}
|
||||
|
||||
@ResponseStatus(value = HttpStatus.NOT_FOUND)
|
||||
@ExceptionHandler(IncorrectResultSizeDataAccessException.class)
|
||||
public ErrorResponse customersNotFound() {
|
||||
return new ErrorResponse("Customer not found");
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/user", method = GET)
|
||||
public ResponseEntity<QuerySideCustomer> getCurrentUser() {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
return ResponseEntity.status(HttpStatus.OK).body(customerAuthService.findByEmail(auth.getName()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
|
||||
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 19.10.15.
|
||||
*/
|
||||
public class AuthRequest {
|
||||
|
||||
@NotBlank
|
||||
@Email
|
||||
private String email;
|
||||
|
||||
public AuthRequest() {
|
||||
}
|
||||
|
||||
public AuthRequest(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
|
||||
|
||||
/**
|
||||
* Created by Main on 17.02.2016.
|
||||
*/
|
||||
public class ErrorResponse {
|
||||
|
||||
private String message;
|
||||
|
||||
public ErrorResponse() {
|
||||
}
|
||||
|
||||
public ErrorResponse(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
14
java-spring/common-auth/build.gradle
Normal file
14
java-spring/common-auth/build.gradle
Normal file
@@ -0,0 +1,14 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
compile project(":common")
|
||||
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
|
||||
|
||||
compile "org.springframework.security:spring-security-config:4.0.2.RELEASE"
|
||||
compile "org.springframework.security:spring-security-web:4.0.2.RELEASE"
|
||||
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.token.KeyBasedPersistenceTokenService;
|
||||
import org.springframework.security.core.token.TokenService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.09.15.
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableWebSecurity
|
||||
@EnableMongoRepositories
|
||||
@EnableConfigurationProperties({AuthProperties.class})
|
||||
public class AuthConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private AuthProperties securityProperties;
|
||||
|
||||
@Autowired
|
||||
private TokenAuthenticationService tokenAuthenticationService;
|
||||
|
||||
@Autowired
|
||||
CustomerAuthService customerAuthService;
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
//auth.inMemoryAuthentication();
|
||||
auth.userDetailsService(userDetailsServiceBean());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetailsService userDetailsServiceBean() {
|
||||
return email -> {
|
||||
/* QuerySideCustomer customer = customerAuthService.findByEmail(email);
|
||||
if (customer != null) {
|
||||
return new User(email);
|
||||
} else {
|
||||
throw new UsernameNotFoundException(String.format("could not find the customer '%s'", email));
|
||||
}*/
|
||||
//authorize everyone with basic authentication
|
||||
return new User(email, "", true, true, true, true,
|
||||
AuthorityUtils.createAuthorityList("USER"));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CustomerAuthService customerAuthService(CustomerAuthRepository customerAuthRepository) {
|
||||
return new CustomerAuthService(customerAuthRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
.httpBasic().and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/index.html", "/", "/**.js", "/**.css").permitAll()
|
||||
.antMatchers("/swagger-ui.html", "/v2/api-docs").permitAll()
|
||||
.antMatchers(HttpMethod.POST, "/api/customers", "/api/login").permitAll()
|
||||
.anyRequest().authenticated().and()
|
||||
.addFilterAfter(new StatelessAuthenticationFilter(tokenAuthenticationService), BasicAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenService tokenService() {
|
||||
KeyBasedPersistenceTokenService res = new KeyBasedPersistenceTokenService();
|
||||
res.setSecureRandom(new SecureRandom());
|
||||
res.setServerSecret(securityProperties.getServerSecret());
|
||||
res.setServerInteger(securityProperties.getServerInteger());
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 21.09.15.
|
||||
*/
|
||||
@ConfigurationProperties(locations = "classpath:auth.properties", ignoreUnknownFields = false, prefix = "auth")
|
||||
public class AuthProperties {
|
||||
private String serverSecret;
|
||||
private Integer serverInteger;
|
||||
|
||||
public String getServerSecret() {
|
||||
return serverSecret;
|
||||
}
|
||||
|
||||
public void setServerSecret(String serverSecret) {
|
||||
this.serverSecret = serverSecret;
|
||||
}
|
||||
|
||||
public Integer getServerInteger() {
|
||||
return serverInteger;
|
||||
}
|
||||
|
||||
public void setServerInteger(Integer serverInteger) {
|
||||
this.serverInteger = serverInteger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
interface CustomerAuthRepository extends MongoRepository<QuerySideCustomer, String> {
|
||||
|
||||
List<QuerySideCustomer> findByEmail(String email);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Main on 15.02.2016.
|
||||
*/
|
||||
public class CustomerAuthService {
|
||||
private CustomerAuthRepository customerAuthRepository;
|
||||
|
||||
public CustomerAuthService(CustomerAuthRepository customerAuthRepository) {
|
||||
this.customerAuthRepository = customerAuthRepository;
|
||||
}
|
||||
|
||||
public QuerySideCustomer findByEmail(String email) {
|
||||
List<QuerySideCustomer> customers = customerAuthRepository.findByEmail(email);
|
||||
if (customers.isEmpty())
|
||||
throw new EmptyResultDataAccessException(1);
|
||||
//TODO: add unique email constraint
|
||||
/* else if(customers.size()>1)
|
||||
throw new IncorrectResultSizeDataAccessException(1, customers.size());*/
|
||||
else
|
||||
return customers.get(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.UserAuthentication;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.token.Token;
|
||||
import org.springframework.security.core.token.TokenService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 23.09.15.
|
||||
*/
|
||||
@Service
|
||||
public class TokenAuthenticationService {
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
private static final String AUTH_HEADER_NAME = "access-token";
|
||||
private static final long DAY = 1000 * 60 * 60 * 24;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public Authentication getAuthentication(HttpServletRequest request) throws IOException {
|
||||
final String tokenString = request.getHeader(AUTH_HEADER_NAME);
|
||||
|
||||
if (tokenString != null) {
|
||||
Token token = tokenService.verifyToken(tokenString);
|
||||
final User user = mapper.readValue(token.getExtendedInformation(), User.class);
|
||||
|
||||
if (user != null && (System.currentTimeMillis() - token.getKeyCreationTime()) < DAY) {
|
||||
return new UserAuthentication(user);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.TokenAuthenticationService;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 23.09.15.
|
||||
*/
|
||||
public class StatelessAuthenticationFilter extends GenericFilterBean {
|
||||
|
||||
private final TokenAuthenticationService tokenAuthenticationService;
|
||||
|
||||
public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
|
||||
this.tokenAuthenticationService = taService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
SecurityContextHolder.getContext().setAuthentication(
|
||||
tokenAuthenticationService.getAuthentication((HttpServletRequest) req));
|
||||
}
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 23.09.15.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class User implements UserDetails {
|
||||
|
||||
private String email;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.email = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("USER");
|
||||
Set<GrantedAuthority> res = new HashSet<GrantedAuthority>();
|
||||
res.add(authority);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 23.09.15.
|
||||
*/
|
||||
public class UserAuthentication implements Authentication {
|
||||
|
||||
private final User user;
|
||||
private boolean authenticated = true;
|
||||
|
||||
public UserAuthentication(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return user.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return user.getAuthorities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return user.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getDetails() {
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return user.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
auth.serverSecret=the_cake_is_a_lie
|
||||
auth.serverInteger=1
|
||||
@@ -1,11 +1,13 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
|
||||
compile project(":common")
|
||||
|
||||
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
|
||||
|
||||
testCompile "junit:junit:4.11"
|
||||
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
|
||||
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AccountChangedEvent implements Event {
|
||||
protected BigDecimal amount;
|
||||
protected EntityIdentifier transactionId;
|
||||
protected String transactionId;
|
||||
|
||||
public AccountChangedEvent(BigDecimal amount, EntityIdentifier transactionId) {
|
||||
public AccountChangedEvent(BigDecimal amount, String transactionId) {
|
||||
this.amount = amount;
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
@@ -18,7 +16,7 @@ public class AccountChangedEvent implements Event {
|
||||
public AccountChangedEvent() {
|
||||
}
|
||||
|
||||
public EntityIdentifier getTransactionId() {
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AccountCreditedEvent extends AccountChangedEvent {
|
||||
@@ -10,7 +7,7 @@ public class AccountCreditedEvent extends AccountChangedEvent {
|
||||
private AccountCreditedEvent() {
|
||||
}
|
||||
|
||||
public AccountCreditedEvent(BigDecimal amount, EntityIdentifier transactionId) {
|
||||
public AccountCreditedEvent(BigDecimal amount, String transactionId) {
|
||||
super(amount, transactionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
public class AccountDebitFailedDueToInsufficientFundsEvent implements Event {
|
||||
private EntityIdentifier transactionId;
|
||||
private String transactionId;
|
||||
|
||||
private AccountDebitFailedDueToInsufficientFundsEvent() {
|
||||
}
|
||||
|
||||
public AccountDebitFailedDueToInsufficientFundsEvent(EntityIdentifier transactionId) {
|
||||
public AccountDebitFailedDueToInsufficientFundsEvent(String transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public EntityIdentifier getTransactionId() {
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
|
||||
import net.chrisrichardson.eventstore.Aggregate;
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AccountDebitedEvent extends AccountChangedEvent {
|
||||
@@ -10,7 +7,7 @@ public class AccountDebitedEvent extends AccountChangedEvent {
|
||||
private AccountDebitedEvent() {
|
||||
}
|
||||
|
||||
public AccountDebitedEvent(BigDecimal amount, EntityIdentifier transactionId) {
|
||||
public AccountDebitedEvent(BigDecimal amount, String transactionId) {
|
||||
super(amount, transactionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AccountOpenedEvent implements Event {
|
||||
|
||||
private String customerId;
|
||||
private String title;
|
||||
private BigDecimal initialBalance;
|
||||
private String description;
|
||||
|
||||
private AccountOpenedEvent() {
|
||||
}
|
||||
|
||||
public AccountOpenedEvent(BigDecimal initialBalance) {
|
||||
public AccountOpenedEvent(String customerId, String title, BigDecimal initialBalance, String description) {
|
||||
this.customerId = customerId;
|
||||
this.title = title;
|
||||
this.initialBalance = initialBalance;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public BigDecimal getInitialBalance() {
|
||||
return initialBalance;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@net.chrisrichardson.eventstore.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account")
|
||||
@io.eventuate.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account")
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
|
||||
|
||||
/**
|
||||
* Created by Main on 08.02.2016.
|
||||
*/
|
||||
public class CustomerAddedToAccount extends CustomerEvent {
|
||||
|
||||
private ToAccountInfo toAccountInfo;
|
||||
|
||||
public CustomerAddedToAccount() {
|
||||
}
|
||||
|
||||
public CustomerAddedToAccount(ToAccountInfo toAccountInfo) {
|
||||
this.toAccountInfo = toAccountInfo;
|
||||
}
|
||||
|
||||
public ToAccountInfo getToAccountInfo() {
|
||||
return toAccountInfo;
|
||||
}
|
||||
|
||||
public void setToAccountInfo(ToAccountInfo toAccountInfo) {
|
||||
this.toAccountInfo = toAccountInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
|
||||
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 02.02.16.
|
||||
*/
|
||||
public class CustomerCreatedEvent extends CustomerEvent {
|
||||
|
||||
private CustomerInfo customerInfo;
|
||||
|
||||
public CustomerCreatedEvent() {
|
||||
}
|
||||
|
||||
public CustomerCreatedEvent(CustomerInfo customerInfo) {
|
||||
this.customerInfo = customerInfo;
|
||||
}
|
||||
|
||||
public CustomerInfo getCustomerInfo() {
|
||||
return customerInfo;
|
||||
}
|
||||
|
||||
public void setCustomerInfo(CustomerInfo customerInfo) {
|
||||
this.customerInfo = customerInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
|
||||
|
||||
|
||||
import io.eventuate.Event;
|
||||
import io.eventuate.EventEntity;
|
||||
|
||||
/**
|
||||
* Created by Main on 11.02.2016.
|
||||
*/
|
||||
@EventEntity(entity = "net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer")
|
||||
public abstract class CustomerEvent implements Event {
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
public class CreditRecordedEvent implements Event {
|
||||
private TransferDetails details;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
// import io.eventuate.Event;
|
||||
|
||||
import io.eventuate.Event;
|
||||
|
||||
public class DebitRecordedEvent implements Event {
|
||||
private TransferDetails details;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
public class FailedDebitRecordedEvent implements Event {
|
||||
private TransferDetails details;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
|
||||
|
||||
|
||||
import net.chrisrichardson.eventstore.Event;
|
||||
import io.eventuate.Event;
|
||||
|
||||
public class MoneyTransferCreatedEvent implements Event {
|
||||
private TransferDetails details;
|
||||
|
||||
@@ -4,34 +4,50 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.trans
|
||||
case class TransferDetails(fromAccountId : EntityIdentifier, toAccountId : EntityIdentifier, amount : BigDecimal)
|
||||
*/
|
||||
|
||||
import net.chrisrichardson.eventstore.EntityIdentifier;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
public class TransferDetails {
|
||||
|
||||
private EntityIdentifier fromAccountId;
|
||||
private EntityIdentifier toAccountId;
|
||||
private String fromAccountId;
|
||||
private String toAccountId;
|
||||
private BigDecimal amount;
|
||||
private Date date;
|
||||
private String description;
|
||||
|
||||
private TransferDetails() {
|
||||
}
|
||||
|
||||
public TransferDetails(EntityIdentifier fromAccountId, EntityIdentifier toAccountId, BigDecimal amount) {
|
||||
public TransferDetails(String fromAccountId, String toAccountId, BigDecimal amount) {
|
||||
this(fromAccountId, toAccountId, amount, new Date(), "");
|
||||
}
|
||||
|
||||
public TransferDetails(String fromAccountId, String toAccountId, BigDecimal amount, Date date, String description) {
|
||||
this.fromAccountId = fromAccountId;
|
||||
this.toAccountId = toAccountId;
|
||||
this.amount = amount;
|
||||
this.date = date;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public EntityIdentifier getFromAccountId() {
|
||||
public String getFromAccountId() {
|
||||
return fromAccountId;
|
||||
}
|
||||
|
||||
public EntityIdentifier getToAccountId() {
|
||||
public String getToAccountId() {
|
||||
return toAccountId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@net.chrisrichardson.eventstore.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer")
|
||||
@io.eventuate.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer")
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
|
||||
@@ -16,7 +16,7 @@
|
||||
<logger name="org.springframework" level='info'>
|
||||
</logger>
|
||||
|
||||
<logger name="net.chrisrichardson.eventstore.client" level='info'>
|
||||
<logger name="io.eventuate" level='debug'>
|
||||
</logger>
|
||||
|
||||
</configuration>
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
|
||||
|
||||
|
||||
import io.eventuate.javaclient.commonimpl.JSonMapper;
|
||||
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
|
||||
import net.chrisrichardson.eventstore.json.EventStoreCommonObjectMapping;
|
||||
import net.chrisrichardson.utils.json.JSonMapper;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
public class AccountOpenEventSerializationTest {
|
||||
|
||||
public class AccountOpenEventSerializationTest {
|
||||
|
||||
@Test
|
||||
public void shouldSerde() {
|
||||
AccountOpenedEvent event = new AccountOpenedEvent(new BigDecimal(55));
|
||||
String json = JSonMapper.toJson(event, EventStoreCommonObjectMapping.getObjectMapper());
|
||||
|
||||
AccountOpenedEvent event = new AccountOpenedEvent("00000000-00000000", "My Account", new BigDecimal(55), "");
|
||||
String json = JSonMapper.toJson(event);
|
||||
System.out.println("json=" + json);
|
||||
|
||||
AccountOpenedEvent event2 = JSonMapper.fromJSon(AccountOpenedEvent.class, json, EventStoreCommonObjectMapping.getObjectMapper());
|
||||
AccountOpenedEvent event2 = JSonMapper.fromJson(json, AccountOpenedEvent.class);
|
||||
|
||||
Assert.assertEquals(event.getInitialBalance(), event2.getInitialBalance());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
|
||||
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
|
||||
|
||||
compile "io.springfox:springfox-swagger2:2.2.2"
|
||||
compile 'io.springfox:springfox-swagger-ui:2.2.2'
|
||||
|
||||
@@ -6,14 +6,14 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import rx.Observable;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.schema.WildcardType;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static springfox.documentation.schema.AlternateTypeRules.newRule;
|
||||
|
||||
@Configuration
|
||||
@@ -27,7 +27,7 @@ public class CommonSwaggerConfiguration {
|
||||
.apis(RequestHandlerSelectors.basePackage("net.chrisrichardson.eventstore.javaexamples.banking"))
|
||||
.build()
|
||||
.pathMapping("/")
|
||||
.genericModelSubstitutes(ResponseEntity.class, Observable.class)
|
||||
.genericModelSubstitutes(ResponseEntity.class, CompletableFuture.class)
|
||||
.alternateTypeRules(
|
||||
newRule(typeResolver.resolve(DeferredResult.class,
|
||||
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.util;
|
||||
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import rx.Observable;
|
||||
import rx.Subscriber;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class DeferredUtils {
|
||||
|
||||
public static <T> DeferredResult<T> toDeferredResult(Observable<T> o) {
|
||||
final DeferredResult<T> d = new DeferredResult<T>();
|
||||
final AtomicReference<T> r = new AtomicReference<T>();
|
||||
|
||||
o.single().subscribe(new Subscriber<T>() {
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
d.setResult(r.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
d.setErrorResult(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T t) {
|
||||
r.set(t);
|
||||
}
|
||||
});
|
||||
return d;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.web.util;
|
||||
|
||||
import rx.Observable;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.web.context.request.async.WebAsyncUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
|
||||
public class ObservableReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return Observable.class.equals(returnType.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
DeferredResult<?> d = DeferredUtils.toDeferredResult((Observable<?>) returnValue);
|
||||
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(d, mavContainer);
|
||||
}
|
||||
|
||||
}
|
||||
8
java-spring/common/build.gradle
Normal file
8
java-spring/common/build.gradle
Normal file
@@ -0,0 +1,8 @@
|
||||
apply plugin: 'java'
|
||||
|
||||
dependencies {
|
||||
compile "commons-lang:commons-lang:2.6"
|
||||
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
|
||||
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
|
||||
|
||||
import org.apache.commons.lang.builder.EqualsBuilder;
|
||||
import org.apache.commons.lang.builder.HashCodeBuilder;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class AccountChangeInfo {
|
||||
|
||||
private String changeId;
|
||||
private String transactionId;
|
||||
private String transactionType;
|
||||
private long amount;
|
||||
private long balanceDelta;
|
||||
|
||||
public AccountChangeInfo() {
|
||||
}
|
||||
|
||||
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
|
||||
this(new Date(), changeId, transactionId, transactionType, amount, balanceDelta);
|
||||
}
|
||||
|
||||
public AccountChangeInfo(Date date, String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
|
||||
this.changeId = changeId;
|
||||
this.transactionId = transactionId;
|
||||
this.transactionType = transactionType;
|
||||
this.amount = amount;
|
||||
this.balanceDelta = balanceDelta;
|
||||
}
|
||||
|
||||
public String getChangeId() {
|
||||
return changeId;
|
||||
}
|
||||
|
||||
public void setChangeId(String changeId) {
|
||||
this.changeId = changeId;
|
||||
}
|
||||
|
||||
public String getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public void setTransactionId(String transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public String getTransactionType() {
|
||||
return transactionType;
|
||||
}
|
||||
|
||||
public void setTransactionType(String transactionType) {
|
||||
this.transactionType = transactionType;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public long getBalanceDelta() {
|
||||
return balanceDelta;
|
||||
}
|
||||
|
||||
public void setBalanceDelta(long balanceDelta) {
|
||||
this.balanceDelta = balanceDelta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return EqualsBuilder.reflectionEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HashCodeBuilder.reflectionHashCode(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Created by popikyardo on 9/1/16.
|
||||
*/
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "entryType")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = AccountTransactionInfo.class, name = "transaction"),
|
||||
@JsonSubTypes.Type(value = AccountOpenInfo.class, name = "account")
|
||||
})
|
||||
public class AccountHistoryEntry {
|
||||
|
||||
protected Date date;
|
||||
protected EntryType entryType;
|
||||
|
||||
public AccountHistoryEntry() {
|
||||
}
|
||||
|
||||
public AccountHistoryEntry(Date date, EntryType entryType) {
|
||||
this.date = date;
|
||||
this.entryType = entryType;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public EntryType getEntryType() {
|
||||
return entryType;
|
||||
}
|
||||
|
||||
public void setEntryType(EntryType entryType) {
|
||||
this.entryType = entryType;
|
||||
}
|
||||
|
||||
public enum EntryType {
|
||||
transaction, account
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user