161 Commits

Author SHA1 Message Date
Chris Richardson
7dcafe5dea Merge pull request #34 from dartpopikyardo/event-sourcing-examples-33
updated Eventuate versions and the app Docker containers
2017-01-21 11:20:35 -08:00
dartpopikyardo
1a2378b14f updated eventuate-local docker images 2017-01-19 02:53:37 +03:00
dartpopikyardo
e1e37359dd - fixed repositories 2017-01-19 02:33:16 +03:00
dartpopikyardo
2ca2fb029e - updated eventuate libs version
- updated docker-compose script: added a restart config to the app services
2017-01-19 02:22:52 +03:00
dartpopikyardo
4c6417ae4e Merge remote-tracking branch 'remotes/upstream/master' 2017-01-19 02:05:41 +03:00
Chris Richardson
edcfd9e22f Updated READMEs 2017-01-10 17:59:54 -08:00
Chris Richardson
6c841000bb Simplified project structure 2017-01-10 17:55:05 -08:00
Chris Richardson
cc65f39088 Fixed buildscript issues 2017-01-10 17:17:10 -08:00
dartpopikyardo
24cc85790c Merge remote-tracking branch 'remotes/upstream/master' 2017-01-01 10:52:01 +03:00
Chris Richardson
d2077e21aa Added link to guide to setting DOCKER_HOST_IP 2016-11-17 12:17:24 -08:00
dartpopikyardo
ab1ba8b274 - fixed logging
- updated AuthConfiguration
2016-10-26 19:31:30 +03:00
Chris Richardson
083047cd81 Merge pull request #26 from dartpopikyardo/master
Сonverted all docker-compose files to use build: rather than volumes
2016-10-26 06:43:08 -07:00
dartpopikyardo
d168a8be2b Merge branch 'private-event-sourcing-examples-51' 2016-10-25 18:26:51 +03:00
dartpopikyardo
1fbd1be4b8 Merge branch 'wip-customer' 2016-10-25 17:44:25 +03:00
dartpopikyardo
56cadd3139 - fixed AccountInfoUpdateService 2016-10-21 21:43:31 +03:00
dartpopikyardo
d30adea37c - fixed AccountInfoUpdateServiceTest 2016-10-21 21:36:32 +03:00
dartpopikyardo
c157ba5720 - fixed AccountInfoUpdateServiceTest 2016-10-21 21:34:21 +03:00
dartpopikyardo
fd0a4355a7 - updated eventuateClientVersion
- updated Int128 usage
2016-10-21 21:29:17 +03:00
Chris Richardson
f750d9c01a Update README.md 2016-10-21 07:08:41 -07:00
dartpopikyardo
909ad5fea9 Merge remote-tracking branch 'remotes/upstream/master' into wip-customer 2016-10-19 19:47:57 +03:00
dartpopikyardo
5bd3f36bd8 fixed build-and-test-all script 2016-10-13 21:15:14 +03:00
dartpopikyardo
903a4b07a3 - added docker-compose build 2016-10-11 22:35:33 +03:00
dartpopikyardo
623554b550 - simplified Dockerfiles 2016-10-11 22:03:17 +03:00
dartpopikyardo
f4a7dbe204 fixed docker-compose script 2016-10-10 21:55:27 +03:00
dartpopikyardo
c8b0185591 fixed docker-compose script 2016-10-10 21:36:31 +03:00
dartpopikyardo
106ee9ee54 fixed docker-compose script 2016-10-10 21:21:16 +03:00
dartpopikyardo
1c0ae8a38c converted all docker-compose files to use build: rather than volumes for images 2016-10-10 21:05:35 +03:00
dartpopikyardo
db05a80e01 fixed logback files 2016-10-07 20:10:51 +03:00
dartpopikyardo
e143d0f49b - improved build-and-test-all-eventuate-local.sh and _build-and-test-all.sh: use EVENTUATE_LOCAL environment variable 2016-10-03 22:50:34 +03:00
Chris Richardson
60a1986f56 Improved wording 2016-10-02 06:59:31 -07:00
Chris Richardson
e75b00b07a Fixed README.md 2016-10-02 06:44:17 -07:00
Chris Richardson
4185064d35 Simplified README.md 2016-09-30 16:42:30 -07:00
crcinc
8b7146479a Merge pull request #56 from dartandrevinsky/private-event-sourcing-examples-46
[WIP] Issue #46 - e2e test scenarios
2016-09-30 16:13:32 -07:00
Chris Richardson
bb9e40e964 Merge pull request #58 from dartpopikyardo/private-event-sourcing-examples-57
update EventuateDependencyPlugin.groovy
2016-09-28 10:32:04 -07:00
Chris Richardson
c2e4df440b Merge pull request #53 from dartpopikyardo/private-event-sourcing-examples-38
Resolve issues with PR
2016-09-28 10:31:46 -07:00
Andrew Revinsky (DART)
823d571015 private-event-sourcing-examples-46 Write a single, simple protractor test:
- [WIP] Create 3rd Party Account (+cases)
2016-09-28 05:02:32 +03:00
Andrew Revinsky (DART)
01bc1709c3 private-event-sourcing-examples-46 Write a single, simple protractor test:
- Sign Up (+cases)
- Login (+cases)
- Create Account (+cases)
- [WIP] Create 3rd Party Account (+cases)
2016-09-28 04:42:01 +03:00
Andrew Revinsky (DART)
ebca25a2c8 Merge commit '68a126908895da2e62ddc8955e1c8790195a6e9b' into private-event-sourcing-examples-46
* commit '68a126908895da2e62ddc8955e1c8790195a6e9b':
  fix AuthControllerIntegrationTest
  fixed a non-404 responce in the case when the customer is not found
  fixed issue #54
2016-09-27 21:17:35 +03:00
dartpopikyardo
68a1269088 fix AuthControllerIntegrationTest 2016-09-27 16:14:21 +03:00
dartpopikyardo
4b74d2c1f1 fixed a non-404 responce in the case when the customer is not found 2016-09-27 16:08:11 +03:00
dartpopikyardo
7260376b17 Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-46' into private-event-sourcing-examples-46 2016-09-27 15:54:52 +03:00
dartpopikyardo
4b78658dd8 fixed issue #54 2016-09-27 11:20:47 +03:00
dartpopikyardo
04fe9045ea Merge remote-tracking branch 'remotes/upstream/wip-customer' into private-event-sourcing-examples-38 2016-09-27 10:26:02 +03:00
Andrew Revinsky (DART)
29cfdb0abf private-event-sourcing-examples-46 Write a single, simple protractor test + Sign Up & Login + [WIP] (errors on auth) 2016-09-26 20:27:31 +03:00
dartpopikyardo
790c76c602 private-event-sourcing-examples-57 update EventuateDependencyPlugin.groovy 2016-09-26 18:41:09 +03:00
Andrew Revinsky (DART)
f740098f5c Merge commit 'e01e98a50875f85f98a80376f1580b1c901bef38' into private-event-sourcing-examples-46
* commit 'e01e98a50875f85f98a80376f1580b1c901bef38':
  - fix issue #48 - fix issue #52
2016-09-26 14:07:56 +03:00
Andrew Revinsky (DART)
85e6c5788d Merge branch 'private-event-sourcing-examples-46' of https://github.com/dartandrevinsky/private-event-sourcing-examples into private-event-sourcing-examples-46
* 'private-event-sourcing-examples-46' of https://github.com/dartandrevinsky/private-event-sourcing-examples:
  [WIP] signupPage e2e test scenario
  private-event-sourcing-examples-46 Write a single, simple protractor test
2016-09-23 22:24:40 +03:00
Andrew Revinsky (DART)
e94e24d7d8 [WIP] signupPage e2e test scenario 2016-09-23 22:24:17 +03:00
Andrew Revinsky (DART)
93a51f531b private-event-sourcing-examples-46 Write a single, simple protractor test 2016-09-23 22:24:17 +03:00
Chris Richardson
b2d135665c Simplified dependencies 2016-09-23 08:38:20 -07:00
dartpopikyardo
e01e98a508 Merge remote-tracking branch 'remotes/upstream/wip-customer' into private-event-sourcing-examples-38 2016-09-23 17:34:27 +03:00
dartpopikyardo
e4a0d6773d - fix issue #48
- fix issue #52
2016-09-23 17:02:54 +03:00
Chris Richardson
727c771add Merge pull request #49 from dartandrevinsky/private-event-sourcing-examples-38
Account deletion + UI improvements
2016-09-23 06:49:31 -07:00
Andrew Revinsky (DART)
abeb29bb2b Merge branch 'private-event-sourcing-examples-38' into private-event-sourcing-examples-46
* private-event-sourcing-examples-38:
  removed redundant EventHandler method from accounts-commandside-backend
  fixed getDate()
  private-event-sourcing-examples-26 solved comments on previous commit: - changed getting Content-Type header for GatewayController - updated transaction state change logic - renamed "date" field in AccountInfo
2016-09-22 21:39:19 +03:00
Andrew Revinsky (DART)
0c573e9124 Merge branch 'wip-customer' into private-event-sourcing-examples-38
* wip-customer:
  fixed getDate()
  private-event-sourcing-examples-26 solved comments on previous commit: - changed getting Content-Type header for GatewayController - updated transaction state change logic - renamed "date" field in AccountInfo

# Conflicts:
#	java-spring/accounts-query-side-backend/src/main/java/net/chrisrichardson/eventstore/javaexamples/banking/backend/queryside/accounts/AccountInfoUpdateService.java
#	java-spring/api-gateway-service/src/main/java/net/chrisrichardson/eventstore/javaexamples/banking/apigateway/controller/GatewayController.java
2016-09-22 21:31:57 +03:00
Andrew Revinsky (DART)
00f5b5a3ae Merge commit '2c97630aa6100ac8b507b71c4a3468540996f136' into wip-customer
* commit '2c97630aa6100ac8b507b71c4a3468540996f136':
  Improved Eventuate Local support
  fixed getDate()
  private-event-sourcing-examples-26 solved comments on previous commit: - changed getting Content-Type header for GatewayController - updated transaction state change logic - renamed "date" field in AccountInfo
  Fixed typo
  Fixed typo
  Added Swagger to monolithic deployment, Added mongodb-cli.sh
  Fixed typo
  Use specific Java 8 Docker image
2016-09-22 21:14:24 +03:00
Andrew Revinsky (DART)
2db8e09570 [WIP] signupPage e2e test scenario 2016-09-22 21:13:03 +03:00
Chris Richardson
2c97630aa6 Merge pull request #39 from dartpopikyardo/private-event-sourcing-examples-37
Add 3d party accounts and previous PR fixes
2016-09-22 09:41:04 -07:00
Andrew Revinsky (DART)
334df47112 private-event-sourcing-examples-46 Write a single, simple protractor test 2016-09-20 21:36:02 +03:00
dartpopikyardo
ab4026a0e9 removed redundant EventHandler method from accounts-commandside-backend 2016-09-20 18:37:48 +03:00
Andrew Revinsky (DART)
d5d6fb95d0 Eternal loading for deleted accounts - fixed 2016-09-20 15:15:01 +03:00
dartpopikyardo
f4b35d04e9 added JAVA_OPTS 2016-09-20 11:37:59 +03:00
dartpopikyardo
50fdad2328 Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-38' into private-event-sourcing-examples-38 2016-09-20 11:22:35 +03:00
dartpopikyardo
974f2b991a updated AbstractRestAPITest 2016-09-20 10:41:41 +03:00
dartpopikyardo
b06d607b6e fixed typo in api-gateway properties 2016-09-20 04:14:37 +03:00
dartpopikyardo
a947cbf287 merged with upstream/wip-customer 2016-09-20 04:01:23 +03:00
dartpopikyardo
473743dbc9 merged with upstream/wip-customer 2016-09-20 04:01:01 +03:00
dartpopikyardo
0f21a057b0 - splitted account deletion to 2 separate endpoints: /api/customers/{customerId}/toaccounts/{accountId} and /api/accounts/{accountId}
- simplified the tests
- cleared the code
2016-09-20 03:55:44 +03:00
Andrew Revinsky (DART)
5fd5b71f06 Delete 3rd P/Account - endpoints
Error reporting
Unneeded links
JS error
2016-09-19 19:46:05 +03:00
Chris Richardson
c88ba88063 Improved Eventuate Local support 2016-09-16 18:30:24 -07:00
dartpopikyardo
fe925ac14a fix issue #31 2016-09-17 00:51:44 +03:00
dartpopikyardo
8312cb309b Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-38' into private-event-sourcing-examples-38 2016-09-16 23:53:22 +03:00
Andrew Revinsky (DART)
5c96941c05 Issue #42, endpoints fixed 2016-09-16 23:48:25 +03:00
dartpopikyardo
93fc13e7a8 fixed GatewayController 2016-09-16 20:51:16 +03:00
Andrew Revinsky (DART)
459e2915a3 Code overhaul & cleanup 2016-09-16 19:14:16 +03:00
dartpopikyardo
fe20a3fe90 added dummmy fields to AccountDeletedEvent 2016-09-16 16:03:47 +03:00
dartpopikyardo
b0bfa7f29f fixed api gateway properties 2016-09-15 20:48:57 +03:00
dartpopikyardo
61a115351e uncomment test methods 2016-09-15 20:34:49 +03:00
dartpopikyardo
fe220a8499 Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-38' into private-event-sourcing-examples-38 2016-09-15 18:54:36 +03:00
dartpopikyardo
f4ec434997 fixed tests 2016-09-15 18:46:49 +03:00
Andrew Revinsky (DART)
5f11423993 Account & bookmarks deletion - bug fixed 2016-09-15 18:46:43 +03:00
dartpopikyardo
6c2700baba Merge remote-tracking branch 'remotes/dartandrevinsky/private-event-sourcing-examples-38' into private-event-sourcing-examples-38 2016-09-15 18:13:20 +03:00
Andrew Revinsky (DART)
e551b3d77c Account & bookmarks deletion 2016-09-15 18:06:49 +03:00
Andrew Revinsky (DART)
c52b803c3a Merge branch 'wip-customer' into private-event-sourcing-examples-38
* wip-customer:
  Form validation for Sign In & Sign Up
  Error reporting for Sign In & Sign Up
  - shouldCreateAccountsAndTransferMoney fix
  Password on login & registration
  - fixed tests fix issue #24, fix issue #26, fix issue #27, fix issue #28
  - added password to CustomerInfo - added unique email constraint to CustomerQuerySide - updated authorization logic
  removed transferStates from AccountInfo cannot reproduce issue #37
2016-09-15 17:06:20 +03:00
dartpopikyardo
8f4ac83f4b added timestamp to make AccountDeletedEvent not empty 2016-09-15 16:55:56 +03:00
dartpopikyardo
72a4ab1ee4 should fix issue #38 2016-09-14 23:02:15 +03:00
dartpopikyardo
91f6fde5cd should fix issue #23 2016-09-13 22:02:17 +03:00
Andrew Revinsky (DART)
20cc17c68c Form validation for Sign In & Sign Up 2016-09-13 18:49:40 +03:00
Andrew Revinsky (DART)
0b39ba658a Error reporting for Sign In & Sign Up 2016-09-13 01:54:56 +03:00
dartpopikyardo
df4204f9a2 - shouldCreateAccountsAndTransferMoney fix 2016-09-09 21:14:33 +03:00
Andrew Revinsky (DART)
258646a4d6 Password on login & registration 2016-09-09 19:03:35 +03:00
Andrew Revinsky (DART)
12cc48b906 Merge commit '859a01a4bc2372a11a5377d1206235ca12da4417' into wip-customer
* commit '859a01a4bc2372a11a5377d1206235ca12da4417':
  - fixed tests fix issue #24, fix issue #26, fix issue #27, fix issue #28
  - added password to CustomerInfo - added unique email constraint to CustomerQuerySide - updated authorization logic
  removed transferStates from AccountInfo cannot reproduce issue #37
  Revert "wip-customer small issues fixes"
2016-09-09 18:38:32 +03:00
dartpopikyardo
859a01a4bc - fixed tests
fix issue #24,
fix issue #26,
fix issue #27,
fix issue #28
2016-09-09 16:55:24 +03:00
dartpopikyardo
fd63640307 - added password to CustomerInfo
- added unique email constraint to CustomerQuerySide
- updated authorization logic
2016-09-08 22:17:41 +03:00
dartpopikyardo
c916bc85eb fixed getDate() 2016-09-07 20:08:36 +03:00
dartpopikyardo
f9d81802f7 Merge remote-tracking branch 'remotes/origin/wip-customer' into private-event-sourcing-examples-37 2016-09-07 19:55:09 +03:00
dartpopikyardo
4f1bb4aa52 Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-09-07 19:53:36 +03:00
dartpopikyardo
7c47d590df private-event-sourcing-examples-26
solved comments on previous commit:
- changed getting Content-Type header for GatewayController
- updated transaction state change logic
- renamed "date" field in AccountInfo
2016-09-07 19:50:17 +03:00
Andrew Revinsky (DART)
886956348b Transfers history - 3rd party transfers & links to accounts 2016-09-07 19:14:29 +03:00
dartpopikyardo
8b5b54ed01 removed transferStates from AccountInfo
cannot reproduce issue #37
2016-09-07 18:09:38 +03:00
dartpopikyardo
732581a3cb Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-09-07 15:35:17 +03:00
Andrew Revinsky (DART)
192db2bfa5 Transfers history - 3rd party transfers 2016-09-07 15:26:00 +03:00
Chris Richardson
8ae6db7229 Merge branch 'master' into wip-eventuate-client-java
Conflicts:
	java-spring/monolithic-service/build.gradle
	java-spring/monolithic-service/src/main/java/net/chrisrichardson/eventstore/javaexamples/banking/web/BankingWebConfiguration.java
2016-09-06 21:51:17 -07:00
Chris Richardson
c03a5fed8d Merge pull request #36 from dartpopikyardo/wip-customer
UI updates, backend transaction history fixes
2016-09-06 20:49:04 -07:00
dartpopikyardo
ee45163f2c merged with shopcookeat/private-event-sourcing-examples wip-customer 2016-09-06 21:53:05 +03:00
Andrew Revinsky (DART)
e6bf638b4b Transfers history - correct statuses and calculation 2016-09-06 20:29:47 +03:00
dartpopikyardo
d6f60101db - updated AccountInfoUpdateService: fixed simultaneous upsert overrides 2016-09-06 19:06:40 +03:00
Andrew Revinsky (DART)
e54577d656 REST endpoints should return an JSON object, not an Array. #14 2016-09-06 02:46:19 +03:00
dartpopikyardo
bae00f6bd7 private-event-sourcing-examples-8 GatewayController ignores http status code returned by destination server
- added http status and headers to GatewayController's response
2016-09-05 22:53:56 +03:00
dartpopikyardo
68dca23a6b updated AccountInfoUpdateService: changed addTransaction saving-to-mongo logic 2016-09-05 22:28:19 +03:00
dartpopikyardo
19c9f88a7f wip issue #8 2016-09-05 15:17:28 +03:00
dartpopikyardo
9b6956b8df wip issue #8 2016-09-05 14:46:48 +03:00
dartpopikyardo
e2de325df2 wip issue #8 2016-09-05 14:31:14 +03:00
dartpopikyardo
c9fa916cdd fixed issue #22
fixed issue #35
2016-09-05 12:05:48 +03:00
dartpopikyardo
f79ebb4d18 Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-09-02 23:53:11 +03:00
dartpopikyardo
8f2fc83a34 wip issue #22 - fixed api gateway controller's request mapping 2016-09-02 23:52:29 +03:00
Andrew Revinsky (DART)
05f2f309e7 Transaction history - step 1 2016-09-02 23:14:52 +03:00
dartpopikyardo
6cf774da2a wip issue #22 2016-09-02 15:39:03 +03:00
dartpopikyardo
3c2e9d374c wip issue #22 2016-09-02 15:32:55 +03:00
dartpopikyardo
55cb34ef4f fix issue #22 2016-09-02 15:11:44 +03:00
dartpopikyardo
dde554e442 reverted back AccountInfo and AccountInfoUpdateService changes 2016-09-02 11:53:50 +03:00
Chris Richardson
fd75779093 Merge pull request #33 from shopcookeat/revert-32-wip-customer
Revert "wip-customer small issues fixes"
2016-09-01 16:12:28 -07:00
Chris Richardson
c8291bec71 Revert "wip-customer small issues fixes" 2016-09-01 16:11:53 -07:00
Chris Richardson
1005c47d83 Merge pull request #32 from dartpopikyardo/wip-customer
wip-customer small issues fixes
2016-09-01 15:53:08 -07:00
dartpopikyardo
47e9053285 wip on issue #22 2016-09-02 01:45:30 +03:00
dartpopikyardo
f76912a6cf fix issue #13,
fix issue #14,
fix issue #15,
fix issue #17
2016-09-02 01:24:28 +03:00
dartpopikyardo
625ea6007e fix issue #7,
fix issue #8,
fix issue #9,
fix issue #11
2016-09-01 22:34:26 +03:00
Chris Richardson
6fae59fdeb Merge pull request #12 from dartandrevinsky/wip-customer
Changes to the app
2016-09-01 08:26:24 -07:00
dartpopikyardo
f846a32d95 fix issue #5,
fix issue #6
2016-09-01 16:36:48 +03:00
dartpopikyardo
2a712017f1 - updated {accountId}/history controller 2016-09-01 16:06:51 +03:00
dartpopikyardo
85613936f4 Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-09-01 09:34:28 +03:00
dartpopikyardo
6f480ad11a fix issue #10 2016-09-01 09:10:03 +03:00
dartpopikyardo
2b0c405378 fixed tests 2016-09-01 08:08:30 +03:00
dartpopikyardo
a5d1e7312c Merge remote-tracking branch 'remotes/upstream/wip-eventuate-local' into wip-customer 2016-09-01 02:39:47 +03:00
Andrew Revinsky (DART)
1b53bd9147 Refresh after account creation 2016-08-31 19:40:09 +03:00
Andrew Revinsky (DART)
d1328e4ce8 Merge commit '45bda8e14d32cf016ac5dd105828f2cda073e7aa' into wip-customer
* commit '45bda8e14d32cf016ac5dd105828f2cda073e7aa':
  Fixed misc @Configuration issues and commented out incorrect version check that causing failure in AccountInfoUpdateService
  - fixed test config
  - disable vertx file cache
  -changed path to UI static content
  Merge remote-tracking branch 'remotes/upstream/wip-eventuate-local' into wip-customer
  Upgraded to Eventuate Local 0.2.0.RELEASE
  Bumped version
  Support for Eventuate Local
2016-08-31 11:57:34 +03:00
dartpopikyardo
45bda8e14d Merge last changes from 'remotes/upstream/wip-customer' into wip-customer 2016-08-31 10:06:05 +03:00
Andrew Revinsky (DART)
d9e13ff669 Improve the UI #2 2016-08-31 03:52:35 +03:00
Chris Richardson
e14787bce8 Fixed misc @Configuration issues and commented out incorrect version check that causing failure in AccountInfoUpdateService 2016-08-30 16:26:21 -07:00
dartpopikyardo
1d14ece9cf - fixed test config 2016-08-30 23:13:32 +03:00
dartpopikyardo
1e13d482a8 - disable vertx file cache 2016-08-30 22:37:55 +03:00
dartpopikyardo
2467099c3e -changed path to UI static content 2016-08-30 19:14:03 +03:00
dartpopikyardo
f4ec33d275 Merge remote-tracking branch 'remotes/upstream/wip-eventuate-local' into wip-customer 2016-08-30 19:07:57 +03:00
dartpopikyardo
a91ade08b1 Merge remote-tracking branch 'remotes/upstream/wip-eventuate-local' into wip-customer 2016-08-30 19:05:44 +03:00
Andrew Revinsky (DART)
fb97767a06 Improve the UI #2 2016-08-26 20:32:10 +03:00
Andrew Revinsky (DART)
1e7234166a Changes to the app 2016-08-26 14:13:50 +03:00
Chris Richardson
ef444bde9b Fixed API gateway misconfiguration 2016-08-25 14:48:30 -07:00
Chris Richardson
4b73b9beed Misc bug fixes 2016-08-25 14:21:44 -07:00
Chris Richardson
d0bdd51406 Fixed typo 2016-08-25 12:15:54 -07:00
Chris Richardson
b0b32d0a3f Merge pull request #22 from dartpopikyardo/wip-customer
refactored the tests
2016-08-25 12:14:35 -07:00
Main
e9517fe30f unified code format 2016-08-24 21:22:12 +03:00
Main
5f8475d675 - fixed the tests
- updated eventuate-client version
- fixed api-gateway and common module's dependencies
2016-08-10 23:39:00 +03:00
Main
12e46582cd Merge remote-tracking branch 'remotes/upstream/wip-customer' into wip-customer
# Conflicts:
#	java-spring/monolithic-service/src/test/java/net/chrisrichardson/eventstore/javaexamples/banking/web/BankingWebIntegrationTest.java
2016-08-09 22:59:59 +03:00
Main
b0855ebd81 - refactored the tests
- moved some dto classes into common module
2016-08-09 22:58:00 +03:00
Main
39309f23a1 - refactored the tests
- moved some dto classes into common module
2016-08-09 22:43:41 +03:00
Chris Richardson
e488df3d06 Fixed typo 2016-06-10 16:45:02 -07:00
Chris Richardson
4f11433390 Merge branch 'master' of github.com:cer/event-sourcing-examples 2016-06-10 16:43:32 -07:00
Chris Richardson
b573027fc2 Added Swagger to monolithic deployment, Added mongodb-cli.sh 2016-06-10 16:43:14 -07:00
Chris Richardson
3117f12402 Fixed typo 2016-06-06 20:48:58 -07:00
Chris Richardson
5b029d8307 Merge branch 'master' of github.com:cer/event-sourcing-examples 2016-05-31 15:08:46 -07:00
Chris Richardson
6d8376cfd5 Use specific Java 8 Docker image 2016-05-31 15:08:34 -07:00
367 changed files with 69481 additions and 5141 deletions

114
README.md
View File

@@ -1,7 +1,7 @@
#Event-Sourcing+CQRS example application # 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 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). event sourcing (ES) and command query responsibility segregation (CQRS).
Applications consist of loosely coupled components that communicate using events. 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. These components can be deployed either as separate services or packaged as a monolithic application for simplified development and testing.
@@ -35,99 +35,89 @@ The following diagram shows the architecture:
![Money transfer architecture](https://github.com/cer/event-sourcing-examples/wiki/i/applicationarchitecture.png) ![Money transfer architecture](https://github.com/cer/event-sourcing-examples/wiki/i/applicationarchitecture.png)
There are four logical services: There are the following services:
* Accounts (command-side) - REST API for creating accounts * Customers Service - REST API for creating customers
* Money transfers (command-side) - REST API for transferring money * Accounts Service - REST API for creating accounts
* Account view updater (query-side) - subscribes to events and updates a MongoDB View * Transactions Service - REST API for transferring money
* Account view reader (query-side) - REST API for retrieving accounts * Customers View Service - subscribes to events and updates a MongoDB View, and provides an API for retrieving customers
* Accounts View Service - subscribes to events and updates a MongoDB View, and provides an API for retrieving accounts
One of the neat things about the modular architecture is that there are two ways to deploy these four services: There is also an [API gateway](http://microservices.io/patterns/apigateway.html) service that acts as a Facade in front of the services.
* monolithic-service - all services are packaged as a single Spring Boot executable JAR
* Microservices - three separate Spring Boot executable JARs
* accounts-command-side-service - command-side accounts
* transactions-command-side-service - command-side money transfers
* accounts-query-side-service - Account View Updater and Account View Reader
# About the examples # About the examples
There are currently the following versions of the example application: There are currently the following versions of the example application:
* java-spring - a Java and Spring Boot example * java-spring - a Java and Spring Boot example
* scala-spring - a Scala and Spring Boot example * scala-spring - a Scala and Spring Boot example (NOTE: this version is lagging the Java Spring and hasn't been updated in a longtime.)
Other examples will be added shortly including a Scala/Play example. Other examples will be added shortly including a Scala/Play example.
For more information, please see the [wiki](../../wiki) For more information, please see the [wiki](../../wiki)
# About the Event Store # About the Eventuate Platform
The application uses one of two event stores: The application is built using [Eventuate](http://eventuate.io/), which is an application platform for writing transactional microservices.
It provides a simple yet powerful event-driven programming model that is based on event sourcing and Command Query Responsibility Segregation (CQRS).
Eventuate solves the distributed data management problems inherent in a microservice architecture.
It consists of a scalable, distributed event store and client libraries for various languages and frameworks including Java, Scala, and the Spring framework.
* Embedded SQL-based event store, which is great for integration tests. There are two versions of Eventuate:
It is also used when running the monolithic version of the application.
* Event Store server - this is a full featured event store.
See this [wiki page](../../wiki/AboutTheEventStoreServer) for more details.
# Building the application (and running the tests) * [Eventuate SaaS server](http://eventuate.io/usingeventuate.html) - this is a full featured event store that is hosted on AWS
* [Eventuate Local](http://eventuate.io/usingeventuate.html) - an open-source event store that is built using MySQL and Kafka
Both versions of the application use Gradle. There is also an embedded test event store, which is great for integration tests.
To build an application, execute this command in the application's top-level directory:
# Building and running the microservices
This is a Gradle project.
However, you do not need to install Gradle since it will be downloaded automatically.
You just need to have Java 8 installed.
The details of how to build and run the services depend slightly on whether you are using Eventuate SaaS or Eventuate Local.
## Building and running using Eventuate SaaS
First, must [sign up to get your credentials](https://signup.eventuate.io/) in order to get free access to the SaaS version.
Next, build the application
``` ```
cd java-spring
./gradlew assemble ./gradlew assemble
``` ```
Note: you do not need to install Gradle. Next, you can launch the services using [Docker Compose](https://docs.docker.com/compose/):
It will be automatically downloaded by `./gradlew`.
This will build a Spring Boot jar in each of the `*-service` directories.
You can also run the tests using `gradle build`.
However, you must set some environment variables.
First, you need to tell the query side code how to connect to MongoDB:
``` ```
export SPRING_DATA_MONGODB_URI=mongodb://192.168.59.103/yourdb docker-compose up -d
``` ```
[Docker Compose](https://docs.docker.com/compose/) is a great way to run MongoDB. Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080`
You can run the `docker-compose up -d mongodb` to run MongoDB.
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. Note: `DOCKER_HOST_IP` is the IP address of the machine where Docker is running, e.g. the IP address of the VirtualBox VM.
But don't worry.
The build is configured to ignore failures for those projects.
# Running the application ## Building and running using Eventuate Local
To run the application, you must to set the SPRING_DATA_MONGODB_URI environment variable, which tells the query services how to connect to MongoDB. First, build the application
There are a couple of different ways of running the application.
## Running the monolithic application
One option is to run the self-contained monolithic application.
It uses the embedded event store.
Simply use this command:
``` ```
java -jar monolithic-service/build/libs/monolithic-service.jar cd java-spring
./gradlew assemble -P eventuateDriver=local
``` ```
## Running the microservices Next, launch the services using [Docker Compose](https://docs.docker.com/compose/):
The other option is to run the services separately. ```
However, in order to do this you need to [get credentials for the Event Store](../../wiki/AboutTheEventStoreServer). export DOCKER_HOST_IP=...
docker-compose -f docker-compose-eventuate-local.yml up -d
```
One way to run the services is to use the scripts `run-all-services.sh`, which runs the services, and `kill-all-services.sh`, which kills the processes. Note: You need to set `DOCKER_HOST_IP` before running Docker Compose.
This must be an IP address or resolvable hostname.
It cannot be `localhost`.
See this [guide to setting `DOCKER_HOST_IP`](http://eventuate.io/docs/usingdocker.html) for more information.
A much better way, however, is to use Docker Compose. Finally, you can open the home page, which is served up by the API Gateway: `http://$DOCKER_HOST_IP:8080`
Simply run the command `docker-compose up` to launch the services.
This will create containers for MongoDB and each of the services.
You can now, for example, use the curl commands in `handy-curl-commands.sh` to interact with the server.
You can also use the Swagger UI exposed by each service `http://host:port/swagger-ui.html`.

103
Vagrantfile vendored
View File

@@ -1,103 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.
# Every Vagrant development environment requires a box. You can search for
# boxes at https://atlas.hashicorp.com/search.
config.vm.box = "ubuntu/trusty64"
config.vm.provider "virtualbox" do |v|
v.memory = 2048
v.cpus = 2
end
# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# config.vm.network "forwarded_port", guest: 80, host: 8080
# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"
# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"
# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
# such as FTP and Heroku are also available. See the documentation at
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
# config.push.define "atlas" do |push|
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
# end
# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: <<-SHELL
#!/bin/sh
# https://github.com/pussinboots/vagrant-devel/blob/master/provision/packages/java8.sh
if which java >/dev/null; then
echo "skip java 8 installation"
else
echo "java 8 installation"
apt-get install --yes python-software-properties
add-apt-repository ppa:webupd8team/java
apt-get update -qq
echo debconf shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections
echo debconf shared/accepted-oracle-license-v1-1 seen true | /usr/bin/debconf-set-selections
apt-get install --yes oracle-java8-installer
yes "" | apt-get -f install
fi
SHELL
config.vm.provision "docker" do |d|
end
config.vm.provision "shell", inline: <<-SHELL
if which docker-compose >/dev/null; then
echo "skip docker-compose installation"
else
sudo bash -c "curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose ; chmod +x /usr/local/bin/docker-compose"
fi
SHELL
end

View File

@@ -2,6 +2,17 @@
set -e set -e
if [ -z "$DOCKER_HOST_IP" ] ; then
if [ -z "$DOCKER_HOST" ] ; then
export DOCKER_HOST_IP=`hostname`
else
echo using ${DOCKER_HOST?}
XX=${DOCKER_HOST%\:*}
export DOCKER_HOST_IP=${XX#tcp\:\/\/}
fi
echo set DOCKER_HOST_IP $DOCKER_HOST_IP
fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DOCKER_COMPOSE="docker-compose -p event-sourcing-examples" DOCKER_COMPOSE="docker-compose -p event-sourcing-examples"
@@ -26,16 +37,8 @@ if [ "$1" = "--no-rm" ] ; then
shift shift
fi fi
${DOCKER_COMPOSE?} up -d mongodb ${DOCKER_COMPOSE?} up -d mongodb $EXTRA_INFRASTRUCTURE_SERVICES
if [ -z "$DOCKER_HOST_IP" ] ; then
if which docker-machine >/dev/null; then
export DOCKER_HOST_IP=$(docker-machine ip default)
else
export DOCKER_HOST_IP=localhost
fi
echo set DOCKER_HOST_IP $DOCKER_HOST_IP
fi
if [ -z "$SPRING_DATA_MONGODB_URI" ] ; then if [ -z "$SPRING_DATA_MONGODB_URI" ] ; then
export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP?}/mydb export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP?}/mydb
@@ -44,21 +47,22 @@ fi
export SERVICE_HOST=$DOCKER_HOST_IP export SERVICE_HOST=$DOCKER_HOST_IP
./gradlew $* build -x :e2e-test:test ./gradlew $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* build -x :e2e-test:test
if [ -z "$EVENTUATE_API_KEY_ID" -o -z "$EVENTUATE_API_KEY_SECRET" ] ; then if [ -z "$EVENTUATE_LOCAL" ] && [ -z "$EVENTUATE_API_KEY_ID" -o -z "$EVENTUATE_API_KEY_SECRET" ] ; then
echo You must set EVENTUATE_API_KEY_ID and EVENTUATE_API_KEY_SECRET echo You must set EVENTUATE_API_KEY_ID and EVENTUATE_API_KEY_SECRET
exit -1 exit -1
fi fi
${DOCKER_COMPOSE?} build
${DOCKER_COMPOSE?} up -d ${DOCKER_COMPOSE?} up -d
$DIR/wait-for-services.sh $DOCKER_HOST_IP 8080 8081 8082 $DIR/wait-for-services.sh $DOCKER_HOST_IP 8080 8081 8082 8083 8084
set -e set -e
./gradlew -a $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false ./gradlew $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS -a $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false
if [ $NO_RM = false ] ; then if [ $NO_RM = false ] ; then
${DOCKER_COMPOSE?} stop ${DOCKER_COMPOSE?} stop

View File

@@ -1,6 +0,0 @@
#! /bin/bash -e
for dir in java-spring scala-spring; do
(cd $dir ; ./gradlew -b build.gradle $*)
done

View File

@@ -2,36 +2,10 @@ This is the Java/Spring version of the Event Sourcing/CQRS money transfer exampl
# About the application # About the application
This application consists of three microservices: This application consists of the following microservices:
* Account Service - the command side business logic for Accounts * Account Service - the command side business logic for Accounts
* Money Transfer Service - the command side business logic for Money Transfers * Account View Service - query side implementation of a MongoDB-based, denormalized view of Accounts
* Query service - query side implementation of a MongoDB-based, denormalized view of Accounts and MoneyTransfers * Customer Service - the command side business logic for Customers
* Customer View Service - query side implementation of a MongoDB-based, denormalized view of Customers
The Account Service consists of the following modules: * Transaction Service - the command side business logic for Money Transfers
* 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
* accounts-query-side-web - a REST API for querying the denormalized view
* accounts-query-side-service - a standalone microservice
# 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.
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

View File

@@ -1,14 +0,0 @@
apply plugin: 'java'
dependencies {
compile project(":common-backend")
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 "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,7 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import io.eventuate.Command;
interface AccountCommand extends Command {
}

View File

@@ -1,20 +0,0 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
dependencies {
compile project(":accounts-command-side-web")
compile project(":common-swagger")
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
testCompile "org.springframework.boot:spring-boot-starter-test"
}
test {
ignoreFailures System.getenv("EVENTUATE_API_KEY_ID") == null
}

View File

@@ -1,11 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.main;
import net.chrisrichardson.eventstore.javaexamples.banking.web.AccountsCommandSideServiceConfiguration;
import org.springframework.boot.SpringApplication;
public class AccountsCommandSideServiceMain {
public static void main(String[] args) {
SpringApplication.run(AccountsCommandSideServiceConfiguration.class, args);
}
}

View File

@@ -1,12 +0,0 @@
dependencies {
compile project(":accounts-command-side-backend")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,29 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
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 java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/accounts")
public class AccountController {
private AccountService accountService;
@Autowired
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
@RequestMapping(method = RequestMethod.POST)
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()));
}
}

View File

@@ -1,16 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({AccountConfiguration.class})
@ComponentScan
@EnableAutoConfiguration
public class CommandSideWebAccountsConfiguration {
}

View File

@@ -1,11 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({CommandSideWebAccountsConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public class AccountControllerIntegrationTestConfiguration {
}

View File

@@ -1,19 +0,0 @@
<?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.web" level='debug'>
</logger>
</configuration>

View File

@@ -1,15 +0,0 @@
apply plugin: 'java'
dependencies {
compile project(":common-backend")
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,31 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
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;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -1,27 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import io.eventuate.CompletableFutureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class AccountQueryService {
private AccountInfoRepository accountInfoRepository;
public AccountQueryService(AccountInfoRepository accountInfoRepository) {
this.accountInfoRepository = accountInfoRepository;
}
public CompletableFuture<AccountInfo> findByAccountId(String accountId) {
AccountInfo account = accountInfoRepository.findOne(accountId);
if (account == null)
return CompletableFutureUtil.failedFuture(new AccountNotFoundException(accountId));
else
return CompletableFuture.completedFuture(account);
}
public CompletableFuture<List<AccountInfo>> findByCustomerId(String customerId) {
return CompletableFuture.completedFuture(accountInfoRepository.findByCustomerId(customerId));
}
}

View File

@@ -1,11 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.main;
import net.chrisrichardson.eventstore.javaexamples.banking.web.AccountsQuerySideServiceConfiguration;
import org.springframework.boot.SpringApplication;
public class AccountsQuerySideServiceMain {
public static void main(String[] args) {
SpringApplication.run(AccountsQuerySideServiceConfiguration.class, args);
}
}

View File

@@ -1,10 +0,0 @@
dependencies {
compile project(":accounts-query-side-backend")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
}

View File

@@ -1,14 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.QuerySideAccountConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({QuerySideAccountConfiguration.class})
@ComponentScan
public class QuerySideWebConfiguration {
}

View File

@@ -1,49 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts;
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.backend.queryside.accounts.AccountTransactionInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@RestController
public class AccountQueryController {
private AccountQueryService accountInfoQueryService;
@Autowired
public AccountQueryController(AccountQueryService accountInfoQueryService) {
this.accountInfoQueryService = accountInfoQueryService;
}
@RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.GET)
public CompletableFuture<GetAccountResponse> get(@PathVariable String accountId) {
return accountInfoQueryService.findByAccountId(accountId)
.thenApply(accountInfo -> new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription()));
}
@RequestMapping(value = "/accounts", method = RequestMethod.GET)
public CompletableFuture<List<GetAccountResponse>> getAccountsForCustomer(@RequestParam("customerId") String customerId) {
return accountInfoQueryService.findByCustomerId(customerId)
.thenApply(accountInfoList -> accountInfoList.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 CompletableFuture<List<AccountTransactionInfo>> getTransactionsHistory(@PathVariable String accountId) {
return accountInfoQueryService.findByAccountId(accountId)
.thenApply(AccountInfo::getTransactions);
}
@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="account not found")
@ExceptionHandler(AccountNotFoundException.class)
public void accountNotFound() {
}
}

View File

@@ -1,53 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts;
import java.math.BigDecimal;
public class GetAccountResponse {
private String accountId;
private BigDecimal balance;
private String title;
private String description;
public GetAccountResponse() {
}
public GetAccountResponse(String accountId, BigDecimal balance, String title, String description) {
this.accountId = accountId;
this.balance = balance;
this.title = title;
this.description = description;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public BigDecimal getBalance() {
return balance;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@@ -0,0 +1,4 @@
FROM java:openjdk-8u91-jdk
CMD java ${JAVA_OPTS} -jar accounts-service.jar
EXPOSE 8080
COPY build/libs/accounts-service.jar .

View File

@@ -1,19 +1,19 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot' apply plugin: 'spring-boot'
dependencies { dependencies {
compile project(":accounts-query-side-web") compile project(":common-backend")
compile project(":common-swagger") compile project(":common-swagger")
compile "org.springframework.boot:spring-boot-starter-web" compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator" compile "org.springframework.boot:spring-boot-starter-actuator"
testCompile project(":testutil") testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test" testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
} }
test { test {

View File

@@ -0,0 +1,21 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice;
import io.eventuate.javaclient.driver.EventuateDriverConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web.AccountsWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({AccountsWebConfiguration.class, EventuateDriverConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class AccountsServiceMain {
public static void main(String[] args) {
SpringApplication.run(AccountsServiceMain.class, args);
}
}

View File

@@ -1,25 +1,31 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.Event; import io.eventuate.Event;
import io.eventuate.EventUtil; import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate; import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitFailedDueToInsufficientFundsEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class Account extends ReflectiveMutableCommandProcessingAggregate<Account, AccountCommand> { public class Account extends ReflectiveMutableCommandProcessingAggregate<Account, AccountCommand> {
private BigDecimal balance; private BigDecimal balance;
private boolean deleted;
public List<Event> process(OpenAccountCommand cmd) { public List<Event> process(OpenAccountCommand cmd) {
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription())); return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription()));
} }
public List<Event> process(DeleteAccountCommand cmd) {
return EventUtil.events(new AccountDeletedEvent());
}
public List<Event> process(DebitAccountCommand cmd) { public List<Event> process(DebitAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
if (balance.compareTo(cmd.getAmount()) < 0) if (balance.compareTo(cmd.getAmount()) < 0)
return EventUtil.events(new AccountDebitFailedDueToInsufficientFundsEvent(cmd.getTransactionId())); return EventUtil.events(new AccountDebitFailedDueToInsufficientFundsEvent(cmd.getTransactionId()));
else else
@@ -27,6 +33,9 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
} }
public List<Event> process(CreditAccountCommand cmd) { public List<Event> process(CreditAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
return EventUtil.events(new AccountCreditedEvent(cmd.getAmount(), cmd.getTransactionId())); return EventUtil.events(new AccountCreditedEvent(cmd.getAmount(), cmd.getTransactionId()));
} }
@@ -34,6 +43,10 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
balance = event.getInitialBalance(); balance = event.getInitialBalance();
} }
public void apply(AccountDeletedEvent event) {
deleted = true;
}
public void apply(AccountDebitedEvent event) { public void apply(AccountDebitedEvent event) {
balance = balance.subtract(event.getAmount()); balance = balance.subtract(event.getAmount());
} }

View File

@@ -0,0 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.Command;
interface AccountCommand extends Command {
}

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.AggregateRepository; import io.eventuate.AggregateRepository;
@@ -19,4 +19,7 @@ public class AccountService {
return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance, description)); return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance, description));
} }
public CompletableFuture<EntityWithIdAndVersion<Account>> deleteAccount(String accountId) {
return accountRepository.update(accountId, new DeleteAccountCommand());
}
} }

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.EntityWithIdAndVersion; import io.eventuate.EntityWithIdAndVersion;
@@ -11,8 +11,8 @@ import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transa
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@EventSubscriber(id="accountEventHandlers") @EventSubscriber(id = "accountEventHandlers")
public class AccountWorkflow { public class AccountWorkflow {
@EventHandlerMethod @EventHandlerMethod
public CompletableFuture<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) { public CompletableFuture<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) {
@@ -34,5 +34,4 @@ public class AccountWorkflow {
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId)); return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId));
} }
} }

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.AggregateRepository; import io.eventuate.AggregateRepository;
import io.eventuate.EventuateAggregateStore; import io.eventuate.EventuateAggregateStore;
@@ -8,7 +8,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
@EnableEventHandlers @EnableEventHandlers
public class AccountConfiguration { public class AccountsBackendConfiguration {
@Bean @Bean
public AccountWorkflow accountWorkflow() { public AccountWorkflow accountWorkflow() {

View File

@@ -1,7 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.Aggregate;
import java.math.BigDecimal; import java.math.BigDecimal;

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import java.math.BigDecimal; import java.math.BigDecimal;

View File

@@ -0,0 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
public class DeleteAccountCommand implements AccountCommand {
}

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import java.math.BigDecimal; import java.math.BigDecimal;

View File

@@ -0,0 +1,35 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.DeleteAccountResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
private AccountService accountService;
@Autowired
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
@RequestMapping(method = RequestMethod.POST)
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()));
}
@RequestMapping(value = "{accountId}", method = RequestMethod.DELETE)
public CompletableFuture<DeleteAccountResponse> deleteAccount(@PathVariable String accountId) {
return accountService.deleteAccount(accountId)
.thenApply(entityAndEventInfo -> new DeleteAccountResponse(accountId));
}
}

View File

@@ -0,0 +1,15 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountsBackendConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({AccountsBackendConfiguration.class})
@ComponentScan
public class AccountsWebConfiguration {
}

View File

@@ -1,7 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountRequest; import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountResponse; import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -25,7 +25,7 @@ public class AccountsCommandSideServiceIntegrationTest {
private int port; private int port;
private String baseUrl(String path) { private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path; return "http://localhost:" + port + "/api" + path;
} }
@Autowired @Autowired
@@ -33,10 +33,9 @@ public class AccountsCommandSideServiceIntegrationTest {
@Test @Test
public void shouldCreateAccountsAndTransferMoney() { public void shouldCreateAccounts() {
BigDecimal initialFromAccountBalance = new BigDecimal(500); BigDecimal initialFromAccountBalance = new BigDecimal(500);
BigDecimal initialToAccountBalance = new BigDecimal(100); BigDecimal initialToAccountBalance = new BigDecimal(100);
BigDecimal amountToTransfer = new BigDecimal(150);
String customerId = "00000000-00000000"; String customerId = "00000000-00000000";
String title = "My Account"; String title = "My Account";
@@ -48,8 +47,6 @@ public class AccountsCommandSideServiceIntegrationTest {
Assert.assertNotNull(fromAccountId); Assert.assertNotNull(fromAccountId);
Assert.assertNotNull(toAccountId); Assert.assertNotNull(toAccountId);
} }

View File

@@ -1,6 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web.AccountsWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -13,8 +15,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
@Configuration @Configuration
@Import({CustomersCommandSideServiceConfiguration.class, AuthConfiguration.class}) @Import({AccountsWebConfiguration.class, AuthConfiguration.class})
public class CustomersCommandSideServiceTestConfiguration { @EnableAutoConfiguration
public class AccountsCommandSideServiceTestConfiguration {
@Bean @Bean
public RestTemplate restTemplate(HttpMessageConverters converters) { public RestTemplate restTemplate(HttpMessageConverters converters) {

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AbstractEntityEventTest; import net.chrisrichardson.eventstorestore.javaexamples.testutil.AbstractEntityEventTest;

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend;
import io.eventuate.Event; import io.eventuate.Event;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -35,7 +35,7 @@ public class AccountControllerIntegrationTest {
@Test @Test
public void shouldCreateAccount() throws Exception { public void shouldCreateAccount() throws Exception {
mockMvc.perform(post("/accounts") mockMvc.perform(post("/api/accounts")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}") .content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}")
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
@@ -44,7 +44,7 @@ public class AccountControllerIntegrationTest {
@Test @Test
public void shouldRejectBadRequest() throws Exception { public void shouldRejectBadRequest() throws Exception {
mockMvc.perform(post("/accounts") mockMvc.perform(post("/api/accounts")
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content("{\"initialBalanceXXX\" : 500}") .content("{\"initialBalanceXXX\" : 500}")
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))

View File

@@ -0,0 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.web;
import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({AccountsWebConfiguration.class, EmbeddedTestAggregateStoreConfiguration.class})
@EnableAutoConfiguration
public class AccountControllerIntegrationTestConfiguration {
}

View File

@@ -0,0 +1,4 @@
FROM java:openjdk-8u91-jdk
CMD java ${JAVA_OPTS} -jar accounts-view-service.jar
EXPOSE 8080
COPY build/libs/accounts-view-service.jar .

View File

@@ -1,18 +1,24 @@
apply plugin: 'java' apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
dependencies { dependencies {
compile project(":common-swagger")
compile project(":common-backend") compile project(":common-backend")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion" compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion" 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 project(":testutil")
testCompile "junit:junit:4.11" testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion" testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}
test {
ignoreFailures System.getenv("EVENTUATE_API_KEY_ID") == null
} }

View File

@@ -0,0 +1,21 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice;
import io.eventuate.javaclient.driver.EventuateDriverConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.web.AccountViewWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import({AccountViewWebConfiguration.class, EventuateDriverConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class AccountsViewServiceMain {
public static void main(String[] args) {
SpringApplication.run(AccountsViewServiceMain.class, args);
}
}

View File

@@ -1,9 +1,10 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import java.util.ArrayList; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Collections; import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import java.util.List; import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import java.util.Map;
import java.util.*;
/** /**
* Created by cer on 11/21/14. * Created by cer on 11/21/14.
@@ -18,11 +19,17 @@ public class AccountInfo {
private List<AccountChangeInfo> changes; private List<AccountChangeInfo> changes;
private Map<String, AccountTransactionInfo> transactions; private Map<String, AccountTransactionInfo> transactions;
private String version; private String version;
@JsonProperty("date")
private Date creationDate;
private AccountInfo() { private AccountInfo() {
} }
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, 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 creationDate) {
this.id = id; this.id = id;
this.customerId = customerId; this.customerId = customerId;
@@ -32,6 +39,7 @@ public class AccountInfo {
this.changes = changes; this.changes = changes;
this.transactions = transactions; this.transactions = transactions;
this.version = version; this.version = version;
this.creationDate = creationDate;
} }
public String getId() { public String getId() {
@@ -65,4 +73,8 @@ public class AccountInfo {
public String getVersion() { public String getVersion() {
return version; return version;
} }
public Date getCreationDate() {
return creationDate;
}
} }

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;

View File

@@ -1,6 +1,10 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import com.mongodb.WriteResult; import com.mongodb.WriteResult;
import io.eventuate.Int128;
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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.DuplicateKeyException;
@@ -9,31 +13,36 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.core.query.Update;
import java.math.BigDecimal; 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 net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.MoneyUtil.toIntegerRepr;
import static org.springframework.data.mongodb.core.query.Criteria.where; import static org.springframework.data.mongodb.core.query.Criteria.where;
public class AccountInfoUpdateService { public class AccountInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
private AccountInfoRepository accountInfoRepository;
private MongoTemplate mongoTemplate; private MongoTemplate mongoTemplate;
public AccountInfoUpdateService(MongoTemplate mongoTemplate) { public AccountInfoUpdateService(AccountInfoRepository accountInfoRepository, MongoTemplate mongoTemplate) {
this.accountInfoRepository = accountInfoRepository;
this.mongoTemplate = mongoTemplate; this.mongoTemplate = mongoTemplate;
} }
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String description, Int128 version) {
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String description, String version) {
try { try {
AccountChangeInfo ci = new AccountChangeInfo();
ci.setAmount(toIntegerRepr(initialBalance));
WriteResult x = mongoTemplate.upsert(new Query(where("id").is(accountId).and("version").exists(false)), WriteResult x = mongoTemplate.upsert(new Query(where("id").is(accountId).and("version").exists(false)),
new Update() new Update()
.set("customerId", customerId) .set("customerId", customerId)
.set("title", title) .set("title", title)
.set("description", description) .set("description", description)
.set("balance", toIntegerRepr(initialBalance)) .set("balance", toIntegerRepr(initialBalance))
.set("version", version), .push("changes", ci)
.set("creationDate", new Date(version.getHi()))
.set("version", version.asString()),
AccountInfo.class); AccountInfo.class);
logger.info("Saved in mongo"); logger.info("Saved in mongo");
@@ -45,11 +54,15 @@ public class AccountInfoUpdateService {
} }
} }
public void delete(String accountId) {
accountInfoRepository.delete(accountId);
}
public void addTransaction(String eventId, String accountId, AccountTransactionInfo ti) {
public void addTransaction(String accountId, AccountTransactionInfo ti) {
mongoTemplate.upsert(new Query(where("id").is(accountId)), mongoTemplate.upsert(new Query(where("id").is(accountId)),
new Update(). new Update().
set("transactions." + eventId, ti), set("transactions." + ti.getTransactionId(), ti),
AccountInfo.class); AccountInfo.class);
} }
@@ -63,5 +76,10 @@ public class AccountInfoUpdateService {
AccountInfo.class); AccountInfo.class);
} }
public void updateTransactionStatus(String accountId, String transactionId, TransferState status) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),
new Update().
set("transactions." + transactionId + ".status", status),
AccountInfo.class);
}
} }

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
public class AccountNotFoundException extends RuntimeException { public class AccountNotFoundException extends RuntimeException {

View File

@@ -0,0 +1,24 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import java.util.List;
public class AccountQueryService {
private AccountInfoRepository accountInfoRepository;
public AccountQueryService(AccountInfoRepository accountInfoRepository) {
this.accountInfoRepository = accountInfoRepository;
}
public AccountInfo findByAccountId(String accountId) {
AccountInfo account = accountInfoRepository.findOne(accountId);
if (account == null)
throw new AccountNotFoundException(accountId);
else
return account;
}
public List<AccountInfo> findByCustomerId(String customerId) {
return accountInfoRepository.findByCustomerId(customerId);
}
}

View File

@@ -1,20 +1,23 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import io.eventuate.DispatchedEvent; import io.eventuate.DispatchedEvent;
import io.eventuate.EventHandlerMethod; import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber; import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountChangedEvent; import io.eventuate.Int128;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.CreditRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent; 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.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr; import static net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.MoneyUtil.toIntegerRepr;
@EventSubscriber(id="querySideEventHandlers") @EventSubscriber(id="querySideEventHandlers")
public class AccountQueryWorkflow { public class AccountQueryWorkflow {
@@ -30,24 +33,31 @@ public class AccountQueryWorkflow {
public void create(DispatchedEvent<AccountOpenedEvent> de) { public void create(DispatchedEvent<AccountOpenedEvent> de) {
AccountOpenedEvent event = de.getEvent(); AccountOpenedEvent event = de.getEvent();
String id = de.getEntityId(); String id = de.getEntityId();
String eventId = de.getEventId().asString(); Int128 eventId = de.getEventId();
logger.info("**************** account version=" + id + ", " + eventId); logger.info("**************** account version={}, {}", id, eventId);
BigDecimal initialBalance = event.getInitialBalance(); BigDecimal initialBalance = event.getInitialBalance();
String customerId = event.getCustomerId(); String customerId = event.getCustomerId();
String title = event.getTitle(); String title = event.getTitle();
String description = event.getDescription(); String description = event.getDescription();
accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId); accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId);
} }
@EventHandlerMethod
public void delete(DispatchedEvent<AccountDeletedEvent> de) {
String id = de.getEntityId();
accountInfoUpdateService.delete(id);
}
@EventHandlerMethod @EventHandlerMethod
public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) { public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
String eventId = de.getEventId().asString(); String eventId = de.getEventId().asString();
String moneyTransferId = de.getEntityId(); String moneyTransferId = de.getEntityId();
String fromAccountId = de.getEvent().getDetails().getFromAccountId(); String fromAccountId = de.getEvent().getDetails().getFromAccountId();
String toAccountId = de.getEvent().getDetails().getToAccountId(); String toAccountId = de.getEvent().getDetails().getToAccountId();
logger.info("**************** account version=" + fromAccountId + ", " + de.getEventId().asString()); logger.info("**************** account version={}, {}", fromAccountId, eventId);
logger.info("**************** account version=" + toAccountId + ", " + de.getEventId().asString()); logger.info("**************** account version={}, {}", toAccountId, eventId);
AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId, AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId,
fromAccountId, fromAccountId,
@@ -56,8 +66,8 @@ public class AccountQueryWorkflow {
de.getEvent().getDetails().getDate(), de.getEvent().getDetails().getDate(),
de.getEvent().getDetails().getDescription()); de.getEvent().getDetails().getDescription());
accountInfoUpdateService.addTransaction(eventId, fromAccountId, ti); accountInfoUpdateService.addTransaction(fromAccountId, ti);
accountInfoUpdateService.addTransaction(eventId, toAccountId, ti); accountInfoUpdateService.addTransaction(toAccountId, ti);
} }
@EventHandlerMethod @EventHandlerMethod
@@ -70,6 +80,36 @@ public class AccountQueryWorkflow {
saveChange(de, +1); saveChange(de, +1);
} }
@EventHandlerMethod
public void updateDebitTransactionState(DispatchedEvent<DebitRecordedEvent> de) {
String transactionId = de.getEntityId();
String fromAccountId = de.getEvent().getDetails().getFromAccountId();
String toAccountId = de.getEvent().getDetails().getToAccountId();
accountInfoUpdateService.updateTransactionStatus(fromAccountId, transactionId, TransferState.DEBITED);
accountInfoUpdateService.updateTransactionStatus(toAccountId, transactionId, TransferState.DEBITED);
}
@EventHandlerMethod
public void updateCreditTransactionState(DispatchedEvent<CreditRecordedEvent> de) {
String transactionId = de.getEntityId();
String fromAccountId = de.getEvent().getDetails().getFromAccountId();
String toAccountId = de.getEvent().getDetails().getToAccountId();
accountInfoUpdateService.updateTransactionStatus(fromAccountId, transactionId, TransferState.COMPLETED);
accountInfoUpdateService.updateTransactionStatus(toAccountId, transactionId, TransferState.COMPLETED);
}
@EventHandlerMethod
public void recordFailed(DispatchedEvent<FailedDebitRecordedEvent> de) {
String transactionId = de.getEntityId();
String fromAccountId = de.getEvent().getDetails().getFromAccountId();
String toAccountId = de.getEvent().getDetails().getToAccountId();
accountInfoUpdateService.updateTransactionStatus(fromAccountId, transactionId, TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS);
accountInfoUpdateService.updateTransactionStatus(toAccountId, transactionId, TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS);
}
public <T extends AccountChangedEvent> void saveChange(DispatchedEvent<T> de, int delta) { public <T extends AccountChangedEvent> void saveChange(DispatchedEvent<T> de, int delta) {
String changeId = de.getEventId().asString(); String changeId = de.getEventId().asString();
String transactionId = de.getEvent().getTransactionId(); String transactionId = de.getEvent().getTransactionId();
@@ -78,9 +118,8 @@ public class AccountQueryWorkflow {
long balanceDelta = amount * delta; long balanceDelta = amount * delta;
AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.getEvent().getClass().getSimpleName(), amount, balanceDelta); AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.getEvent().getClass().getSimpleName(), amount, balanceDelta);
String accountId = de.getEntityId(); String accountId = de.getEntityId();
logger.info("**************** account version=" + accountId + ", " + de.getEventId().asString()); logger.info("**************** account version={}, {}", accountId, de.getEventId().asString());
accountInfoUpdateService.updateBalance(accountId, changeId, balanceDelta, ci); accountInfoUpdateService.updateBalance(accountId, changeId, balanceDelta, ci);
} }
} }

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import io.eventuate.javaclient.spring.EnableEventHandlers; import io.eventuate.javaclient.spring.EnableEventHandlers;
@@ -10,7 +10,7 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie
@Configuration @Configuration
@EnableMongoRepositories @EnableMongoRepositories
@EnableEventHandlers @EnableEventHandlers
public class QuerySideAccountConfiguration { public class AccountViewBackendConfiguration {
@Bean @Bean
public AccountQueryWorkflow accountQueryWorkflow(AccountInfoUpdateService accountInfoUpdateService) { public AccountQueryWorkflow accountQueryWorkflow(AccountInfoUpdateService accountInfoUpdateService) {
@@ -18,8 +18,8 @@ public class QuerySideAccountConfiguration {
} }
@Bean @Bean
public AccountInfoUpdateService accountInfoUpdateService(MongoTemplate mongoTemplate) { public AccountInfoUpdateService accountInfoUpdateService(AccountInfoRepository accountInfoRepository, MongoTemplate mongoTemplate) {
return new AccountInfoUpdateService(mongoTemplate); return new AccountInfoUpdateService(accountInfoRepository, mongoTemplate);
} }
@Bean @Bean
@@ -27,8 +27,6 @@ public class QuerySideAccountConfiguration {
return new AccountQueryService(accountInfoRepository); return new AccountQueryService(accountInfoRepository);
} }
@Bean @Bean
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) { public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
return new QuerySideDependencyChecker(mongoTemplate); return new QuerySideDependencyChecker(mongoTemplate);

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import java.math.BigDecimal; import java.math.BigDecimal;

View File

@@ -1,11 +1,10 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.MongoTemplate;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
public class QuerySideDependencyChecker { public class QuerySideDependencyChecker {
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());

View File

@@ -0,0 +1,64 @@
package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.web;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountNotFoundException;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.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 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;
@Autowired
public AccountQueryController(AccountQueryService accountInfoQueryService) {
this.accountInfoQueryService = accountInfoQueryService;
}
@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.getCreationDate(), 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() {
}
}

View File

@@ -1,9 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web; package net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.web;
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountViewBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.QuerySideWebConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@@ -13,11 +10,9 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration @Configuration
@Import({QuerySideWebConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class}) @Import({AccountViewBackendConfiguration.class})
@EnableAutoConfiguration
@ComponentScan @ComponentScan
public class AccountsQuerySideServiceConfiguration { public class AccountViewWebConfiguration {
@Bean @Bean
public HttpMessageConverters customConverters() { public HttpMessageConverters customConverters() {

View File

@@ -1,9 +1,17 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.web;
import io.eventuate.Int128;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration; import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import io.eventuate.javaclient.spring.jdbc.IdGenerator; import io.eventuate.javaclient.spring.jdbc.IdGenerator;
import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl; import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountInfoUpdateService;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountQueryService;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountViewBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -13,11 +21,9 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date; import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -29,7 +35,7 @@ public class AccountInfoUpdateServiceTest {
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@Import({QuerySideAccountConfiguration.class, EventuateJdbcEventStoreConfiguration.class}) @Import({AccountViewBackendConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public static class AccountInfoUpdateServiceTestConfiguration { public static class AccountInfoUpdateServiceTestConfiguration {
} }
@@ -45,7 +51,7 @@ public class AccountInfoUpdateServiceTest {
IdGenerator x = new IdGeneratorImpl(); IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString(); String accountId = x.genId().asString();
String customerId = x.genId().asString(); String customerId = x.genId().asString();
String version = x.genId().asString(); Int128 version = x.genId();
String title = "Checking account"; String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345"); BigDecimal initialBalance = new BigDecimal("1345");
@@ -53,16 +59,16 @@ public class AccountInfoUpdateServiceTest {
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version); accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId).get(); AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId()); assertEquals(accountId, accountInfo.getId());
assertEquals(customerId, accountInfo.getCustomerId()); assertEquals(customerId, accountInfo.getCustomerId());
assertEquals(title, accountInfo.getTitle()); assertEquals(title, accountInfo.getTitle());
assertEquals(description, accountInfo.getDescription()); assertEquals(description, accountInfo.getDescription());
assertEquals(initialBalance.longValue() * 100, accountInfo.getBalance()); assertEquals(initialBalance.longValue() * 100, accountInfo.getBalance());
assertTrue(accountInfo.getChanges().isEmpty()); assertEquals(1, accountInfo.getChanges().size());
assertTrue(accountInfo.getTransactions().isEmpty()); assertTrue(accountInfo.getTransactions().isEmpty());
assertEquals(version, accountInfo.getVersion()); assertEquals(version.asString(), accountInfo.getVersion());
String changeId = x.genId().asString(); String changeId = x.genId().asString();
@@ -75,19 +81,19 @@ public class AccountInfoUpdateServiceTest {
accountInfoUpdateService.updateBalance(accountId, changeId, 500, accountInfoUpdateService.updateBalance(accountId, changeId, 500,
change); change);
accountInfo = accountQueryService.findByAccountId(accountId).get(); accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(initialBalance.add(new BigDecimal(5)).longValue() * 100, accountInfo.getBalance()); assertEquals(initialBalance.add(new BigDecimal(5)).longValue() * 100, accountInfo.getBalance());
assertFalse(accountInfo.getChanges().isEmpty()); assertFalse(accountInfo.getChanges().isEmpty());
assertEquals(change, accountInfo.getChanges().get(0)); assertEquals(change, accountInfo.getChanges().get(1));
String eventId = x.genId().asString(); String eventId = x.genId().asString();
AccountTransactionInfo ti = new AccountTransactionInfo(transactionId, accountId, accountId, 5, new Date(), "A transfer"); AccountTransactionInfo ti = new AccountTransactionInfo(transactionId, accountId, accountId, 5, new Date(), "A transfer");
accountInfoUpdateService.addTransaction(eventId, accountId, ti); accountInfoUpdateService.addTransaction(accountId, ti);
accountInfo = accountQueryService.findByAccountId(accountId).get(); accountInfo = accountQueryService.findByAccountId(accountId);
assertFalse(accountInfo.getTransactions().isEmpty()); assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(ti, accountInfo.getTransactions().get(0)); assertEquals(ti, accountInfo.getTransactions().get(0));
@@ -98,7 +104,7 @@ public class AccountInfoUpdateServiceTest {
IdGenerator x = new IdGeneratorImpl(); IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString(); String accountId = x.genId().asString();
String customerId = x.genId().asString(); String customerId = x.genId().asString();
String version = x.genId().asString(); Int128 version = x.genId();
String title = "Checking account"; String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345"); BigDecimal initialBalance = new BigDecimal("1345");
@@ -106,7 +112,43 @@ public class AccountInfoUpdateServiceTest {
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version); accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
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();
Int128 version = x.genId();
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());
} }
} }

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web; 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.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -30,7 +30,7 @@ public class AccountsQuerySideServiceIntegrationTest {
private int port; private int port;
private String baseUrl(String path) { private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path; return "http://localhost:" + port + "/api" + path;
} }
@Autowired @Autowired

View File

@@ -1,8 +1,12 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web; package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration; import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.web.AccountViewWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters; import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter;
@@ -13,8 +17,10 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
@Configuration @Configuration
@Import({CustomersQuerySideServiceConfiguration.class, CustomersQuerySideServiceConfiguration.class, AuthConfiguration.class}) @Import({AccountViewWebConfiguration.class, EmbeddedTestAggregateStoreConfiguration.class, CommonSwaggerConfiguration.class})
public class CustomersQuerySideServiceTestConfiguration { @EnableAutoConfiguration
@ComponentScan
public class AccountsQuerySideServiceTestConfiguration {
@Bean @Bean
public RestTemplate restTemplate(HttpMessageConverters converters) { public RestTemplate restTemplate(HttpMessageConverters converters) {

View File

@@ -0,0 +1,4 @@
FROM java:openjdk-8u91-jdk
CMD java ${JAVA_OPTS} -jar api-gateway-service.jar
EXPOSE 8080
COPY build/libs/api-gateway-service.jar .

View File

@@ -1,6 +1,5 @@
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'spring-boot' apply plugin: 'spring-boot'
apply plugin: EventuateDependencyPlugin
dependencies { dependencies {
compile project(":common-auth-web") compile project(":common-auth-web")
@@ -15,7 +14,7 @@ dependencies {
} }
task copyWebStatic(type: Copy) { task copyWebStatic(type: Copy) {
from "../../prebuilt-web-client" from "../../js-frontend/build"
into "build/resources/main/static" into "build/resources/main/static"
} }

View File

@@ -5,56 +5,53 @@ import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List; import java.util.List;
/**
* Created by popikyardo on 15.01.16.
*/
@ConfigurationProperties(prefix = "api.gateway") @ConfigurationProperties(prefix = "api.gateway")
public class ApiGatewayProperties { public class ApiGatewayProperties {
private List<Endpoint> endpoints; private List<Endpoint> endpoints;
public static class Endpoint { public static class Endpoint {
private String path; private String path;
private RequestMethod method; private RequestMethod method;
private String location; private String location;
public Endpoint() { 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() { public Endpoint(String location) {
return endpoints; this.location = location;
} }
public void setEndpoints(List<Endpoint> endpoints) { public String getPath() {
this.endpoints = endpoints; 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;
}
} }

View File

@@ -1,7 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway; package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
@@ -21,30 +20,27 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import java.util.Collections; import java.util.Collections;
/**
* Created by popikyardo on 15.01.16.
*/
@Configuration @Configuration
@ComponentScan @ComponentScan
@EnableAutoConfiguration @EnableAutoConfiguration
@Import({EventuateHttpStompClientConfiguration.class, AuthConfiguration.class}) @Import({AuthConfiguration.class})
@EnableConfigurationProperties({ApiGatewayProperties.class}) @EnableConfigurationProperties({ApiGatewayProperties.class})
public class ApiGatewayServiceConfiguration extends WebMvcConfigurerAdapter { public class ApiGatewayServiceConfiguration extends WebMvcConfigurerAdapter {
@Bean @Bean
public RestTemplate restTemplate(HttpMessageConverters converters) { public RestTemplate restTemplate(HttpMessageConverters converters) {
// we have to define Apache HTTP client to use the PATCH verb // we have to define Apache HTTP client to use the PATCH verb
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/json")); converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/json"));
converter.setObjectMapper(new ObjectMapper()); converter.setObjectMapper(new ObjectMapper());
HttpClient httpClient = HttpClients.createDefault(); HttpClient httpClient = HttpClients.createDefault();
RestTemplate restTemplate = new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(converter)); RestTemplate restTemplate = new RestTemplate(Collections.<HttpMessageConverter<?>>singletonList(converter));
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
restTemplate.setErrorHandler(new RestTemplateErrorHandler()); restTemplate.setErrorHandler(new RestTemplateErrorHandler());
return restTemplate; return restTemplate;
} }
} }

View File

@@ -10,15 +10,15 @@ import java.io.IOException;
public class RestTemplateErrorHandler implements ResponseErrorHandler { public class RestTemplateErrorHandler implements ResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(RestTemplateErrorHandler.class); private static final Logger log = LoggerFactory.getLogger(RestTemplateErrorHandler.class);
@Override @Override
public void handleError(ClientHttpResponse response) throws IOException { public void handleError(ClientHttpResponse response) throws IOException {
log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText()); log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
} }
@Override @Override
public boolean hasError(ClientHttpResponse response) throws IOException { public boolean hasError(ClientHttpResponse response) throws IOException {
return RestUtil.isError(response.getStatusCode()); return RestUtil.isError(response.getStatusCode());
} }
} }

View File

@@ -2,14 +2,11 @@ package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
/**
* Created by popikyardo on 07.12.15.
*/
public class RestUtil { public class RestUtil {
public static boolean isError(HttpStatus status) { public static boolean isError(HttpStatus status) {
HttpStatus.Series series = status.series(); HttpStatus.Series series = status.series();
return (HttpStatus.Series.CLIENT_ERROR.equals(series) return (HttpStatus.Series.CLIENT_ERROR.equals(series)
|| HttpStatus.Series.SERVER_ERROR.equals(series)); || HttpStatus.Series.SERVER_ERROR.equals(series));
} }
} }

View File

@@ -4,6 +4,7 @@ import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGateway
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.ContentRequestTransformer; 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.HeadersRequestTransformer;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.URLRequestTransformer; import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.URLRequestTransformer;
import org.apache.http.Header;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
@@ -12,7 +13,11 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; 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.web.bind.annotation.RequestMapping; 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.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException; import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
@@ -27,49 +32,55 @@ import java.util.stream.Collectors;
import static org.springframework.web.bind.annotation.RequestMethod.*; import static org.springframework.web.bind.annotation.RequestMethod.*;
/**
* Created by popikyardo on 15.01.16.
*/
@RestController @RestController
public class GatewayController { public class GatewayController {
Logger log = LoggerFactory.getLogger(this.getClass()); Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired @Autowired
private ApiGatewayProperties apiGatewayProperties; private ApiGatewayProperties apiGatewayProperties;
private HttpClient httpClient; private HttpClient httpClient;
@PostConstruct @PostConstruct
public void init() { public void init() {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
httpClient = HttpClients.custom() httpClient = HttpClients.custom()
.setConnectionManager(cm) .setConnectionManager(cm)
.build(); .build();
} }
@RequestMapping(value = "/**", method = {GET, POST}) @RequestMapping(value = "/api/**", method = {GET, POST, DELETE})
public String proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException { @ResponseBody
HttpUriRequest proxiedRequest = createHttpUriRequest(request); public ResponseEntity<String> proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException {
log.info("request: {}", proxiedRequest); HttpUriRequest proxiedRequest = createHttpUriRequest(request);
HttpResponse proxiedResponse = httpClient.execute(proxiedRequest); logger.info("request: {}", proxiedRequest);
return read(proxiedResponse.getEntity().getContent()); HttpResponse proxiedResponse = httpClient.execute(proxiedRequest);
} logger.info("Response {}", proxiedResponse.getStatusLine().getStatusCode());
return new ResponseEntity<>(read(proxiedResponse.getEntity().getContent()), makeResponseHeaders(proxiedResponse), HttpStatus.valueOf(proxiedResponse.getStatusLine().getStatusCode()));
private HttpUriRequest createHttpUriRequest(HttpServletRequest request) throws URISyntaxException, NoSuchRequestHandlingMethodException, IOException { }
URLRequestTransformer urlRequestTransformer = new URLRequestTransformer(apiGatewayProperties);
ContentRequestTransformer contentRequestTransformer = new ContentRequestTransformer(); private HttpHeaders makeResponseHeaders(HttpResponse response) {
HeadersRequestTransformer headersRequestTransformer = new HeadersRequestTransformer(); HttpHeaders result = new HttpHeaders();
headersRequestTransformer.setPredecessor(contentRequestTransformer); Header h = response.getFirstHeader("Content-Type");
contentRequestTransformer.setPredecessor(urlRequestTransformer); result.set(h.getName(), h.getValue());
return result;
return headersRequestTransformer.transform(request).build(); }
}
private HttpUriRequest createHttpUriRequest(HttpServletRequest request) throws URISyntaxException, NoSuchRequestHandlingMethodException, IOException {
private String read(InputStream input) throws IOException { URLRequestTransformer urlRequestTransformer = new URLRequestTransformer(apiGatewayProperties);
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) { ContentRequestTransformer contentRequestTransformer = new ContentRequestTransformer();
return buffer.lines().collect(Collectors.joining("\n")); 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"));
} }
}
} }

View File

@@ -7,7 +7,7 @@ import org.springframework.boot.SpringApplication;
* Created by Main on 19.01.2016. * Created by Main on 19.01.2016.
*/ */
public class ApiGatewayServiceMain { public class ApiGatewayServiceMain {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(ApiGatewayServiceConfiguration.class, args); SpringApplication.run(ApiGatewayServiceConfiguration.class, args);
} }
} }

View File

@@ -10,21 +10,18 @@ import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/**
* Created by popikyardo on 21.01.16.
*/
public class ContentRequestTransformer extends ProxyRequestTransformer { public class ContentRequestTransformer extends ProxyRequestTransformer {
@Override @Override
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException { public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
RequestBuilder requestBuilder = predecessor.transform(request); RequestBuilder requestBuilder = predecessor.transform(request);
String requestContent = request.getReader().lines().collect(Collectors.joining("")); String requestContent = request.getReader().lines().collect(Collectors.joining(""));
if(!requestContent.isEmpty()) { if (!requestContent.isEmpty()) {
StringEntity entity = new StringEntity(requestContent, ContentType.APPLICATION_JSON); StringEntity entity = new StringEntity(requestContent, ContentType.APPLICATION_JSON);
requestBuilder.setEntity(entity); requestBuilder.setEntity(entity);
}
return requestBuilder;
} }
return requestBuilder;
}
} }

View File

@@ -8,25 +8,21 @@ import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Enumeration; import java.util.Enumeration;
/**
* Created by popikyardo on 21.01.16.
*/
public class HeadersRequestTransformer extends ProxyRequestTransformer { public class HeadersRequestTransformer extends ProxyRequestTransformer {
@Override
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
RequestBuilder requestBuilder = predecessor.transform(request);
@Override Enumeration<String> headerNames = request.getHeaderNames();
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException { while (headerNames.hasMoreElements()) {
RequestBuilder requestBuilder = predecessor.transform(request); String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
Enumeration<String> headerNames = request.getHeaderNames(); if (headerName.equals("x-access-token")) {
while (headerNames.hasMoreElements()) { requestBuilder.addHeader(headerName, headerValue);
String headerName = headerNames.nextElement(); }
String headerValue = request.getHeader(headerName);
if(headerName.equals("x-access-token")) {
requestBuilder.addHeader(headerName, headerValue);
}
}
return requestBuilder;
} }
return requestBuilder;
}
} }

View File

@@ -7,16 +7,13 @@ import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public abstract class ProxyRequestTransformer { public abstract class ProxyRequestTransformer {
protected ProxyRequestTransformer predecessor; protected ProxyRequestTransformer predecessor;
public abstract RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException; public abstract RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException;
public void setPredecessor(ProxyRequestTransformer transformer) { public void setPredecessor(ProxyRequestTransformer transformer) {
this.predecessor = transformer; this.predecessor = transformer;
}; }
} }

View File

@@ -9,40 +9,37 @@ import javax.servlet.http.HttpServletRequest;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public class URLRequestTransformer extends ProxyRequestTransformer { public class URLRequestTransformer extends ProxyRequestTransformer {
private ApiGatewayProperties apiGatewayProperties; private ApiGatewayProperties apiGatewayProperties;
public URLRequestTransformer(ApiGatewayProperties apiGatewayProperties) { public URLRequestTransformer(ApiGatewayProperties apiGatewayProperties) {
this.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));
} }
@Override RequestBuilder rb = RequestBuilder.create(request.getMethod());
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException { rb.setUri(uri);
String requestURI = request.getRequestURI(); return rb;
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()); private String getServiceUrl(String requestURI, HttpServletRequest httpServletRequest) throws NoSuchRequestHandlingMethodException {
rb.setUri(uri);
return rb;
}
private String getServiceUrl(String requestURI, HttpServletRequest httpServletRequest) throws NoSuchRequestHandlingMethodException { ApiGatewayProperties.Endpoint endpoint =
apiGatewayProperties.getEndpoints().stream()
ApiGatewayProperties.Endpoint endpoint = .filter(e ->
apiGatewayProperties.getEndpoints().stream() requestURI.matches(e.getPath()) && e.getMethod() == RequestMethod.valueOf(httpServletRequest.getMethod())
.filter(e -> )
requestURI.matches(e.getPath()) && e.getMethod() == RequestMethod.valueOf(httpServletRequest.getMethod()) .findFirst().orElseThrow(() -> new NoSuchRequestHandlingMethodException(httpServletRequest));
) return endpoint.getLocation() + requestURI;
.findFirst().orElseThrow(() -> new NoSuchRequestHandlingMethodException(httpServletRequest)); }
return endpoint.getLocation() + requestURI;
}
} }

View File

@@ -5,18 +5,27 @@ customers.queryside.service.host=localhost
transfers.commandside.service.host=localhost transfers.commandside.service.host=localhost
api.gateway.endpoints[0].path=[/]*accounts.* api.gateway.endpoints[0].path=[/]*api/accounts.*
api.gateway.endpoints[0].method=GET api.gateway.endpoints[0].method=GET
api.gateway.endpoints[0].location=http://${accounts.queryside.service.host}:8080 api.gateway.endpoints[0].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[1].path=[/]*accounts.* api.gateway.endpoints[1].path=[/]*api/customers.*/accounts
api.gateway.endpoints[1].method=POST api.gateway.endpoints[1].method=GET
api.gateway.endpoints[1].location=http://${accounts.commandside.service.host}:8080 api.gateway.endpoints[1].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[2].path=[/]*customers.* api.gateway.endpoints[2].path=[/]*api/accounts.*
api.gateway.endpoints[2].method=GET api.gateway.endpoints[2].method=POST
api.gateway.endpoints[2].location=http://${customers.queryside.service.host}:8080 api.gateway.endpoints[2].location=http://${accounts.commandside.service.host}:8080
api.gateway.endpoints[3].path=[/]*customers.* api.gateway.endpoints[3].path=[/]*api/customers.*
api.gateway.endpoints[3].method=POST api.gateway.endpoints[3].method=GET
api.gateway.endpoints[3].location=http://${customers.commandside.service.host}:8080 api.gateway.endpoints[3].location=http://${customers.queryside.service.host}:8080
api.gateway.endpoints[4].path=[/]*transfers.* api.gateway.endpoints[4].path=[/]*api/customers.*
api.gateway.endpoints[4].method=POST api.gateway.endpoints[4].method=POST
api.gateway.endpoints[4].location=http://${transfers.commandside.service.host}:8080 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
api.gateway.endpoints[6].path=[/]*api/customers.*
api.gateway.endpoints[6].method=DELETE
api.gateway.endpoints[6].location=http://${customers.commandside.service.host}:8080
api.gateway.endpoints[7].path=[/]*api/accounts.*
api.gateway.endpoints[7].method=DELETE
api.gateway.endpoints[7].location=http://${accounts.commandside.service.host}:8080

View File

@@ -14,6 +14,11 @@
</root> </root>
<logger name="org.springframework" level='info'> <logger name="org.springframework" level='info'>
</logger> </logger>
<logger name="net.chrisrichardson.eventstore.client" level='info'>
<logger name="net.chrisrichardson.eventstore.javaexamples.banking" level='info'>
</logger> </logger>
<logger name="io.eventuate.activity" level='debug'>
</logger>
</configuration> </configuration>

View File

@@ -2,12 +2,12 @@ apply plugin: VerifyMongoDBConfigurationPlugin
dependencies { dependencies {
testCompile project(":accounts-command-side-backend") testCompile project(":transactions-service")
testCompile project(":transactions-command-side-backend") testCompile project(":accounts-service")
testCompile project(":accounts-query-side-backend") testCompile project(":accounts-view-service")
testCompile project(":customers-command-side-backend") testCompile project(":customers-service")
testCompile project(":customers-query-side-backend") testCompile project(":customers-view-service")
testCompile project(":testutil-customers") testCompile project(":testutil")
testCompile "junit:junit:4.11" testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion" testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"

View File

@@ -1,14 +1,14 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend; package net.chrisrichardson.eventstore.javaexamples.banking.backend;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration; import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountsBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransferBackendConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, EventuateJdbcEventStoreConfiguration.class}) @Import({AccountsBackendConfiguration.class, MoneyTransferBackendConfiguration.class, EmbeddedTestAggregateStoreConfiguration.class})
@EnableAutoConfiguration @EnableAutoConfiguration
public class BankingTestConfiguration { public class BankingTestConfiguration {

View File

@@ -2,12 +2,12 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend;
import io.eventuate.EntityWithIdAndVersion; import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventuateAggregateStore; import io.eventuate.EventuateAggregateStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.Account;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer; import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
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.backend.common.transactions.TransferDetails; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransfer;
import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransferService;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -23,7 +23,7 @@ import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=BankingTestConfiguration.class) @SpringApplicationConfiguration(classes = BankingTestConfiguration.class)
@IntegrationTest @IntegrationTest
public class MoneyTransferIntegrationTest { public class MoneyTransferIntegrationTest {
@@ -35,11 +35,11 @@ public class MoneyTransferIntegrationTest {
@Autowired @Autowired
private EventuateAggregateStore eventStore; private EventuateAggregateStore eventStore;
@Test @Test
public void shouldTransferMoney() { public void shouldTransferMoney() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), "")); final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), "")); final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
@@ -49,22 +49,22 @@ public class MoneyTransferIntegrationTest {
new BigDecimal(80)))); new BigDecimal(80))));
eventually ( eventually(
() -> eventStore.find(Account.class, fromAccount.getEntityId()), () -> eventStore.find(Account.class, fromAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(70), account.getEntity().getBalance())); account -> Assert.assertEquals(new BigDecimal(70), account.getEntity().getBalance()));
eventually ( eventually(
() -> eventStore.find(Account.class, toAccount.getEntityId()), () -> eventStore.find(Account.class, toAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(380), account.getEntity().getBalance())); account -> Assert.assertEquals(new BigDecimal(380), account.getEntity().getBalance()));
eventually ( eventually(
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()), () -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState())); updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
} }
@Test @Test
public void shouldFailDueToInsufficientFunds() { public void shouldFailDueToInsufficientFunds() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), "")); final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), "")); final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
@@ -74,15 +74,15 @@ public class MoneyTransferIntegrationTest {
new BigDecimal(200)))); new BigDecimal(200))));
eventually ( eventually(
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()), () -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
updatedTransaction -> Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.getEntity().getState())); updatedTransaction -> Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.getEntity().getState()));
eventually ( eventually(
() -> eventStore.find(Account.class, fromAccount.getEntityId()), () -> eventStore.find(Account.class, fromAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(150), account.getEntity().getBalance())); account -> Assert.assertEquals(new BigDecimal(150), account.getEntity().getBalance()));
eventually ( eventually(
() -> eventStore.find(Account.class, toAccount.getEntityId()), () -> eventStore.find(Account.class, toAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(300), account.getEntity().getBalance())); account -> Assert.assertEquals(new BigDecimal(300), account.getEntity().getBalance()));

View File

@@ -1,16 +1,14 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import io.eventuate.EntityWithIdAndVersion; import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EntityWithMetadata;
import io.eventuate.EventuateAggregateStore; import io.eventuate.EventuateAggregateStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.Account;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer; import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountQueryService;
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.commandside.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails; import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer; import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransfer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier; import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransferService;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -18,9 +16,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import rx.Observable;
import java.math.BigDecimal; 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.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually; import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@@ -59,10 +57,10 @@ public class AccountQuerySideIntegrationTest {
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState())); updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
eventually( eventually(
() -> accountQueryService.findByAccountId(fromAccount.getEntityId()), () -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(fromAccount.getEntityId())),
accountInfo -> Assert.assertEquals(70*100, accountInfo.getBalance())); accountInfo -> Assert.assertEquals(70 * 100, accountInfo.getBalance()));
eventually( eventually(
() -> accountQueryService.findByAccountId(toAccount.getEntityId()), () -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(toAccount.getEntityId())),
accountInfo -> Assert.assertEquals(380*100, accountInfo.getBalance())); accountInfo -> Assert.assertEquals(380 * 100, accountInfo.getBalance()));
} }
} }

View File

@@ -1,15 +1,16 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts; package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration; import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.accountsservice.backend.AccountsBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.accountsviewservice.backend.AccountViewBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.transactionsservice.backend.MoneyTransferBackendConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Import({AccountConfiguration.class, MoneyTransferConfiguration.class, EventuateJdbcEventStoreConfiguration.class, @Import({AccountsBackendConfiguration.class, MoneyTransferBackendConfiguration.class, EmbeddedTestAggregateStoreConfiguration.class,
QuerySideAccountConfiguration.class}) AccountViewBackendConfiguration.class})
@EnableAutoConfiguration @EnableAutoConfiguration
public class AccountQuerySideTestConfiguration { public class AccountQuerySideTestConfiguration {
} }

View File

@@ -2,10 +2,11 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.cu
import io.eventuate.EntityWithIdAndVersion; import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventuateAggregateStore; 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.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer; import net.chrisrichardson.eventstore.javaexamples.banking.customersservice.backend.Customer;
import net.chrisrichardson.eventstore.javaexamples.banking.customersservice.backend.CustomerService;
import net.chrisrichardson.eventstore.javaexamples.banking.customersviewservice.backend.CustomerQueryService;
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo; import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer; import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier; import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
@@ -19,10 +20,10 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.CompletableFuture; 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.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually; import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.customers.CustomersTestUtils.generateCustomerInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.customers.CustomersTestUtils.generateToAccountInfo;
/** /**
* Created by Main on 10.02.2016. * Created by Main on 10.02.2016.
@@ -32,43 +33,43 @@ import static net.chrisrichardson.eventstorestore.javaexamples.testutil.customer
@IntegrationTest @IntegrationTest
public class CustomerQuerySideIntegrationTest { public class CustomerQuerySideIntegrationTest {
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired @Autowired
private CustomerQueryService customerQueryService; private CustomerQueryService customerQueryService;
@Autowired @Autowired
private EventuateAggregateStore eventStore; private EventuateAggregateStore eventStore;
@Test @Test
public void shouldCreateCustomerAndAddToAccount() throws Exception { public void shouldCreateCustomerAndAddToAccount() throws Exception {
CustomerInfo customerInfo = generateCustomerInfo(); CustomerInfo customerInfo = generateCustomerInfo();
EntityWithIdAndVersion<Customer> customer = await(customerService.createCustomer(customerInfo)); EntityWithIdAndVersion<Customer> customer = await(customerService.createCustomer(customerInfo));
ToAccountInfo toAccountInfo = generateToAccountInfo(); ToAccountInfo toAccountInfo = generateToAccountInfo();
EntityWithIdAndVersion<Customer> customerWithNewAccount = await(customerService.addToAccount(customer.getEntityId(), toAccountInfo)); EntityWithIdAndVersion<Customer> customerWithNewAccount = await(customerService.addToAccount(customer.getEntityId(), toAccountInfo));
eventually( eventually(
new Producer<QuerySideCustomer>() { new Producer<QuerySideCustomer>() {
@Override @Override
public CompletableFuture<QuerySideCustomer> produce() { public CompletableFuture<QuerySideCustomer> produce() {
return customerQueryService.findByCustomerId(customer.getEntityId()); return customerQueryService.findByCustomerId(customer.getEntityId());
} }
}, },
new Verifier<QuerySideCustomer>() { new Verifier<QuerySideCustomer>() {
@Override @Override
public void verify(QuerySideCustomer querySideCustomer) { public void verify(QuerySideCustomer querySideCustomer) {
Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName()); Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName());
Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn()); Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
Assert.assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail()); Assert.assertEquals(customerInfo.getUserCredentials().getEmail(), querySideCustomer.getEmail());
Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber()); Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress()); Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
Assert.assertNotNull(querySideCustomer.getToAccounts()); Assert.assertNotNull(querySideCustomer.getToAccounts());
Assert.assertFalse(querySideCustomer.getToAccounts().isEmpty()); Assert.assertFalse(querySideCustomer.getToAccounts().isEmpty());
Assert.assertEquals(querySideCustomer.getToAccounts().get("11111111-11111111"), toAccountInfo); Assert.assertEquals(querySideCustomer.getToAccounts().get("11111111-11111111"), toAccountInfo);
} }
}); });
} }
} }

View File

@@ -1,14 +1,14 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers; package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration; import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerConfiguration; import net.chrisrichardson.eventstore.javaexamples.banking.customersservice.backend.CustomerBackendConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.customersviewservice.backend.CustomerViewBackendConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@Configuration @Configuration
@Import({CustomerConfiguration.class, EventuateJdbcEventStoreConfiguration.class, QuerySideCustomerConfiguration.class}) @Import({CustomerBackendConfiguration.class, EmbeddedTestAggregateStoreConfiguration.class, CustomerViewBackendConfiguration.class})
@EnableAutoConfiguration @EnableAutoConfiguration
public class CustomerQuerySideTestConfiguration { public class CustomerQuerySideTestConfiguration {
} }

View File

@@ -0,0 +1,6 @@
#! /bin/bash
export JAVA_OPTS="-Xmx128m -Xms128m"
export EXTRA_INFRASTRUCTURE_SERVICES=cdcservice
export EVENTUATE_LOCAL=yes
../_build-and-test-all.sh -f docker-compose-eventuate-local.yml $* -P eventuateDriver=local

View File

@@ -1,3 +1,4 @@
#! /bin/bash #! /bin/bash
export JAVA_OPTS="-Xmx128m -Xms128m"
../_build-and-test-all.sh $* ../_build-and-test-all.sh $*

View File

@@ -23,6 +23,7 @@ subprojects {
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } } eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } }
} }
} }

View File

@@ -6,7 +6,7 @@ class EventuateDependencyPlugin implements Plugin<Project> {
@Override @Override
void apply(Project project) { void apply(Project project) {
project.dependencies { project.dependencies {
if (project.hasProperty("eventuateLocal")) { if (project.hasProperty("eventuateDriver") && project.property("eventuateDriver").equals("local")) {
compile "io.eventuate.local.java:eventuate-local-java-jdbc:${project.eventuateLocalVersion}" compile "io.eventuate.local.java:eventuate-local-java-jdbc:${project.eventuateLocalVersion}"
compile "io.eventuate.local.java:eventuate-local-java-embedded-cdc-autoconfigure:${project.eventuateLocalVersion}" compile "io.eventuate.local.java:eventuate-local-java-embedded-cdc-autoconfigure:${project.eventuateLocalVersion}"
} else } else

View File

@@ -2,10 +2,11 @@ apply plugin: 'java'
dependencies { dependencies {
compile project(":common-auth") compile project(":common-auth")
compile project(":common-customers") compile project(":common")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-security:$springBootVersion" compile "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
testCompile "junit:junit:4.11" testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
} }

View File

@@ -1,12 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller; package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer; import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.UserCredentials;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.CustomerAuthService; 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.ErrorResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User; import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
@@ -23,42 +24,40 @@ import java.io.IOException;
import static org.springframework.web.bind.annotation.RequestMethod.GET; import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* Created by popikyardo on 21.09.15.
*/
@RestController @RestController
@Validated @Validated
@RequestMapping("/api")
public class AuthController { public class AuthController {
@Autowired @Autowired
private TokenService tokenService; private TokenService tokenService;
@Autowired @Autowired
private CustomerAuthService customerAuthService; private CustomerAuthService customerAuthService;
private static ObjectMapper objectMapper = new ObjectMapper(); private static ObjectMapper objectMapper = new ObjectMapper();
@RequestMapping(value = "/login", method = POST) @RequestMapping(value = "/login", method = POST)
public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid AuthRequest request) throws IOException { public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid UserCredentials request) throws IOException {
QuerySideCustomer customer = customerAuthService.findByEmail(request.getEmail()); QuerySideCustomer customer = customerAuthService.findByEmailAndPassword(request.getEmail(), request.getPassword());
Token token = tokenService.allocateToken(objectMapper.writeValueAsString(new User(request.getEmail()))); Token token = tokenService.allocateToken(objectMapper.writeValueAsString(new User(request.getEmail())));
return ResponseEntity.status(HttpStatus.OK).header("access-token", token.getKey()) return ResponseEntity.status(HttpStatus.OK).header("access-token", token.getKey())
.body(customer); .body(customer);
} }
@ResponseStatus(value = HttpStatus.NOT_FOUND) @ResponseStatus(value = HttpStatus.NOT_FOUND)
@ExceptionHandler(IncorrectResultSizeDataAccessException.class) @ExceptionHandler({EmptyResultDataAccessException.class, IncorrectResultSizeDataAccessException.class})
public ErrorResponse customersNotFound() { public ErrorResponse customersNotFound() {
return new ErrorResponse("Customer not found"); return new ErrorResponse("Customer not found");
} }
@RequestMapping(value = "/user", method = GET) @RequestMapping(value = "/user", method = GET)
public ResponseEntity<QuerySideCustomer> getCurrentUser() { public ResponseEntity<QuerySideCustomer> getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.status(HttpStatus.OK).body(customerAuthService.findByEmail(auth.getName())); return ResponseEntity.status(HttpStatus.OK).body(customerAuthService.findByEmail(auth.getName()));
} }
} }

View File

@@ -3,27 +3,36 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotBlank;
/**
* Created by popikyardo on 19.10.15.
*/
public class AuthRequest { public class AuthRequest {
@NotBlank @NotBlank
@Email @Email
private String email; private String email;
public AuthRequest() { @NotBlank
} private String password;
public AuthRequest(String email) { public AuthRequest() {
this.email = email; }
}
public String getEmail() { public AuthRequest(String email, String password) {
return email; this.email = email;
} this.password = password;
}
public void setEmail(String email) { public String getEmail() {
this.email = email; return email;
} }
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
} }

View File

@@ -5,20 +5,20 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
*/ */
public class ErrorResponse { public class ErrorResponse {
private String message; private String message;
public ErrorResponse() { public ErrorResponse() {
} }
public ErrorResponse(String message) { public ErrorResponse(String message) {
this.message = message; this.message = message;
} }
public String getMessage() { public String getMessage() {
return message; return message;
} }
public void setMessage(String message) { public void setMessage(String message) {
this.message = message; this.message = message;
} }
} }

View File

@@ -0,0 +1,80 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Address;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Name;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.CustomerAuthService;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.MediaType;
import org.springframework.security.core.token.DefaultToken;
import org.springframework.security.core.token.TokenService;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = AuthControllerIntegrationTestConfiguration.class)
@IntegrationTest
@WebAppConfiguration
public class AuthControllerIntegrationTest {
private MockMvc mockMvc;
@Mock
TokenService tokenService;
@Mock
CustomerAuthService customerAuthService;
@InjectMocks
AuthController authController;
private static ObjectMapper om = new ObjectMapper();
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(authController).build();
}
@Test
public void shouldLogin() throws Exception {
when(customerAuthService.findByEmailAndPassword("my@email.com", "my_password")).thenReturn(new QuerySideCustomer("id", new Name("test", "test"), "my@email.com", "my_password", "ssn", "", new Address(), null));
when(customerAuthService.findByEmailAndPassword("not_my@email.com", "not_my_password")).thenThrow(new EmptyResultDataAccessException(1));
when(tokenService.allocateToken(om.writeValueAsString(new User("my@email.com")))).thenReturn(new DefaultToken("key", System.currentTimeMillis(), ""));
mockMvc.perform(post("/api/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"email\" : \"my@email.com\", \"password\" : \"my_password\"}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(result -> {
assertTrue(result.getResponse().getContentAsString().contains("id"));
assertTrue(result.getResponse().getContentAsString().contains("my@email.com"));
assertTrue(result.getResponse().getContentAsString().contains("my_password"));
});
mockMvc.perform(post("/api/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"email\" : \"not_my@email.com\", \"password\" : \"not_my_password\"}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
}

View File

@@ -0,0 +1,12 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controller;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(AuthConfiguration.class)
@EnableAutoConfiguration
public class AuthControllerIntegrationTestConfiguration {
}

View File

@@ -1,7 +1,8 @@
apply plugin: 'java' apply plugin: 'java'
dependencies { dependencies {
compile project(":common-customers") compile project(":common")
compile project(":customers-query-side-common")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion" compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"

View File

@@ -1,5 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth; package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter; import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -22,9 +23,6 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
import java.security.SecureRandom; import java.security.SecureRandom;
/**
* Created by popikyardo on 21.09.15.
*/
@Configuration @Configuration
@ComponentScan @ComponentScan
@EnableWebSecurity @EnableWebSecurity
@@ -32,66 +30,62 @@ import java.security.SecureRandom;
@EnableConfigurationProperties({AuthProperties.class}) @EnableConfigurationProperties({AuthProperties.class})
public class AuthConfiguration extends WebSecurityConfigurerAdapter { public class AuthConfiguration extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
private AuthProperties securityProperties; private AuthProperties securityProperties;
@Autowired @Autowired
private TokenAuthenticationService tokenAuthenticationService; private TokenAuthenticationService tokenAuthenticationService;
@Autowired @Autowired
CustomerAuthService customerAuthService; CustomerAuthService customerAuthService;
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication(); auth.userDetailsService(userDetailsServiceBean());
auth.userDetailsService(userDetailsServiceBean()); }
}
@Override @Override
public UserDetailsService userDetailsServiceBean() { public UserDetailsService userDetailsServiceBean() {
return email -> { return email -> {
/* QuerySideCustomer customer = customerAuthService.findByEmail(email); QuerySideCustomer customer = customerAuthService.findByEmail(email);
if (customer != null) { return new User(email, customer.getPassword(), true, true, true, true,
return new User(email); AuthorityUtils.createAuthorityList("USER"));
} 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 @Bean
public CustomerAuthService customerAuthService(CustomerAuthRepository customerAuthRepository) { public CustomerAuthService customerAuthService(CustomerAuthRepository customerAuthRepository) {
return new CustomerAuthService(customerAuthRepository); return new CustomerAuthService(customerAuthRepository);
} }
@Bean @Bean
@Override @Override
public AuthenticationManager authenticationManagerBean() throws Exception { public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); return super.authenticationManagerBean();
} }
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() http
.httpBasic().and() .csrf()
.authorizeRequests() .disable()
.antMatchers("/index.html", "/", "/**.js", "/**.css").permitAll() .httpBasic()
.antMatchers("/swagger-ui.html", "/v2/api-docs").permitAll() .and()
.antMatchers(HttpMethod.POST, "/customers", "/login").permitAll() .authorizeRequests()
.anyRequest().authenticated().and() .antMatchers(HttpMethod.POST, "/api/customers", "/api/login").permitAll()
.addFilterAfter(new StatelessAuthenticationFilter(tokenAuthenticationService), BasicAuthenticationFilter.class); .antMatchers("/api/**").permitAll()
} .anyRequest().permitAll()
.and()
.addFilterAfter(new StatelessAuthenticationFilter(tokenAuthenticationService), BasicAuthenticationFilter.class);
}
@Bean @Bean
public TokenService tokenService() { public TokenService tokenService() {
KeyBasedPersistenceTokenService res = new KeyBasedPersistenceTokenService(); KeyBasedPersistenceTokenService res = new KeyBasedPersistenceTokenService();
res.setSecureRandom(new SecureRandom()); res.setSecureRandom(new SecureRandom());
res.setServerSecret(securityProperties.getServerSecret()); res.setServerSecret(securityProperties.getServerSecret());
res.setServerInteger(securityProperties.getServerInteger()); res.setServerInteger(securityProperties.getServerInteger());
return res; return res;
} }
} }

View File

@@ -2,27 +2,24 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Created by popikyardo on 21.09.15.
*/
@ConfigurationProperties(locations = "classpath:auth.properties", ignoreUnknownFields = false, prefix = "auth") @ConfigurationProperties(locations = "classpath:auth.properties", ignoreUnknownFields = false, prefix = "auth")
public class AuthProperties { public class AuthProperties {
private String serverSecret; private String serverSecret;
private Integer serverInteger; private Integer serverInteger;
public String getServerSecret() { public String getServerSecret() {
return serverSecret; return serverSecret;
} }
public void setServerSecret(String serverSecret) { public void setServerSecret(String serverSecret) {
this.serverSecret = serverSecret; this.serverSecret = serverSecret;
} }
public Integer getServerInteger() { public Integer getServerInteger() {
return serverInteger; return serverInteger;
} }
public void setServerInteger(Integer serverInteger) { public void setServerInteger(Integer serverInteger) {
this.serverInteger = serverInteger; this.serverInteger = serverInteger;
} }
} }

View File

@@ -1,11 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth; package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer; import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List; import java.util.List;
interface CustomerAuthRepository extends MongoRepository<QuerySideCustomer, String> { interface CustomerAuthRepository extends MongoRepository<QuerySideCustomer, String> {
List<QuerySideCustomer> findByEmail(String email); List<QuerySideCustomer> findByEmail(String email);
}
List<QuerySideCustomer> findByEmailAndPassword(String email, String password);
}

View File

@@ -1,29 +1,32 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth; package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer; import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.common.QuerySideCustomer;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.support.DataAccessUtils;
import java.util.List;
/** /**
* Created by Main on 15.02.2016. * Created by Main on 15.02.2016.
*/ */
public class CustomerAuthService { public class CustomerAuthService {
private CustomerAuthRepository customerAuthRepository; private CustomerAuthRepository customerAuthRepository;
public CustomerAuthService(CustomerAuthRepository customerAuthRepository) { public CustomerAuthService(CustomerAuthRepository customerAuthRepository) {
this.customerAuthRepository = customerAuthRepository; this.customerAuthRepository = customerAuthRepository;
} }
public QuerySideCustomer findByEmail(String email){ public QuerySideCustomer findByEmail(String email) {
List<QuerySideCustomer> customers = customerAuthRepository.findByEmail(email); QuerySideCustomer result = DataAccessUtils.uniqueResult(customerAuthRepository.findByEmail(email));
if (customers.isEmpty()) if (result==null)
throw new EmptyResultDataAccessException(1); throw new EmptyResultDataAccessException(1);
//TODO: add unique email constraint
/* else if(customers.size()>1) return result;
throw new IncorrectResultSizeDataAccessException(1, customers.size());*/ }
else
return customers.get(0); public QuerySideCustomer findByEmailAndPassword(String email, String password) {
} QuerySideCustomer result = DataAccessUtils.uniqueResult(customerAuthRepository.findByEmailAndPassword(email, password));
if (result==null)
throw new EmptyResultDataAccessException(1);
return result;
}
} }

View File

@@ -12,32 +12,29 @@ import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
@Service @Service
public class TokenAuthenticationService { public class TokenAuthenticationService {
@Autowired @Autowired
private TokenService tokenService; private TokenService tokenService;
private static final String AUTH_HEADER_NAME = "access-token"; private static final String AUTH_HEADER_NAME = "access-token";
private static final long DAY = 1000 * 60 * 60 * 24; private static final long DAY = 1000 * 60 * 60 * 24;
private ObjectMapper mapper = new ObjectMapper(); private ObjectMapper mapper = new ObjectMapper();
public Authentication getAuthentication(HttpServletRequest request) throws IOException { public Authentication getAuthentication(HttpServletRequest request) throws IOException {
final String tokenString = request.getHeader(AUTH_HEADER_NAME); final String tokenString = request.getHeader(AUTH_HEADER_NAME);
if (tokenString != null) { if (tokenString != null) {
Token token = tokenService.verifyToken(tokenString); Token token = tokenService.verifyToken(tokenString);
final User user = mapper.readValue(token.getExtendedInformation(), User.class); final User user = mapper.readValue(token.getExtendedInformation(), User.class);
if (user != null && (System.currentTimeMillis() - token.getKeyCreationTime()) < DAY) { if (user != null && (System.currentTimeMillis() - token.getKeyCreationTime()) < DAY) {
return new UserAuthentication(user); return new UserAuthentication(user);
} }
}
return null;
} }
return null;
}
} }

View File

@@ -11,23 +11,20 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean { public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService; private final TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService taService) { public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
this.tokenAuthenticationService = taService; this.tokenAuthenticationService = taService;
} }
@Override @Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication()==null) { if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication( SecurityContextHolder.getContext().setAuthentication(
tokenAuthenticationService.getAuthentication((HttpServletRequest) req)); tokenAuthenticationService.getAuthentication((HttpServletRequest) req));
}
chain.doFilter(req, res);
} }
chain.doFilter(req, res);
}
} }

View File

@@ -10,69 +10,66 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
/**
* Created by popikyardo on 23.09.15.
*/
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails { public class User implements UserDetails {
private String email; private String email;
public User() { public User() {
} }
public User(String email) { public User(String email) {
this.email = email; this.email = email;
} }
public void setUsername(String username) { public void setUsername(String username) {
this.email = username; this.email = username;
} }
@Override @Override
@JsonIgnore @JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("USER"); SimpleGrantedAuthority authority = new SimpleGrantedAuthority("USER");
Set<GrantedAuthority> res = new HashSet<GrantedAuthority>(); Set<GrantedAuthority> res = new HashSet<GrantedAuthority>();
res.add(authority); res.add(authority);
return res; return res;
} }
@Override @Override
public String getPassword() { public String getPassword() {
return ""; return "";
} }
@Override @Override
public String getUsername() { public String getUsername() {
return this.email; return this.email;
} }
@Override @Override
public boolean isAccountNonExpired() { public boolean isAccountNonExpired() {
return false; return false;
} }
@Override @Override
public boolean isAccountNonLocked() { public boolean isAccountNonLocked() {
return false; return false;
} }
@Override @Override
public boolean isCredentialsNonExpired() { public boolean isCredentialsNonExpired() {
return false; return false;
} }
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return false; return false;
} }
public String getEmail() { public String getEmail() {
return email; return email;
} }
public void setEmail(String email) { public void setEmail(String email) {
this.email = email; this.email = email;
} }
} }

View File

@@ -5,50 +5,47 @@ import org.springframework.security.core.GrantedAuthority;
import java.util.Collection; import java.util.Collection;
/**
* Created by popikyardo on 23.09.15.
*/
public class UserAuthentication implements Authentication { public class UserAuthentication implements Authentication {
private final User user; private final User user;
private boolean authenticated = true; private boolean authenticated = true;
public UserAuthentication(User user) { public UserAuthentication(User user) {
this.user = user; this.user = user;
} }
@Override @Override
public String getName() { public String getName() {
return user.getUsername(); return user.getUsername();
} }
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities(); return user.getAuthorities();
} }
@Override @Override
public Object getCredentials() { public Object getCredentials() {
return user.getPassword(); return user.getPassword();
} }
@Override @Override
public User getDetails() { public User getDetails() {
return user; return user;
} }
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
return user.getUsername(); return user.getUsername();
} }
@Override @Override
public boolean isAuthenticated() { public boolean isAuthenticated() {
return authenticated; return authenticated;
} }
@Override @Override
public void setAuthenticated(boolean authenticated) { public void setAuthenticated(boolean authenticated) {
this.authenticated = authenticated; this.authenticated = authenticated;
} }
} }

View File

@@ -1,45 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.utils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
/**
* Created by Main on 18.02.2016.
*/
public class BasicAuthUtils {
public static HttpHeaders basicAuthHeaders(String username) {
return new HttpHeaders() {
{
String auth = username + ":";
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
set("Authorization", authHeader);
}
};
}
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType) {
return doBasicAuthenticatedRequest(restTemplate, url, httpMethod, responseType, null);
}
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, Object requestObject) {
HttpEntity httpEntity;
if(requestObject!=null) {
httpEntity = new HttpEntity(requestObject, BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
} else {
httpEntity = new HttpEntity(BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
}
return restTemplate.exchange(url,
httpMethod,
httpEntity,
responseType).getBody();
}
}

Some files were not shown because too many files have changed in this diff Show More