177 Commits

Author SHA1 Message Date
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
Chris Richardson
29d42fda9a Simplified TestUtil, Fixed MongoDB update issue 2016-08-31 13:02:44 -07: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
Chris Richardson
f4e070e7bd Upgraded to Eventuate Local 0.2.0.RELEASE 2016-08-18 13:53:04 -07: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
bd3de1a938 Bumped version 2016-08-06 13:26:08 -07:00
Chris Richardson
f570ccbe90 Support for Eventuate Local 2016-08-05 16:41:16 -07:00
Chris Richardson
fe0ce037de Merge branch 'dartpopikyardo-wip-customer' into wip-customer 2016-08-05 15:15:26 -07:00
Chris Richardson
1e40b2a591 Misc changes 2016-08-05 15:14:51 -07:00
Main
f1d97ac49c -updated test configuration 2016-07-26 20:32:58 +03:00
Main
b2b68ce163 Merge remote-tracking branch 'remotes/upstream/wip-eventuate-client-java' into wip-customer 2016-07-25 23:06:05 +03:00
Chris Richardson
e486102018 Upgraded to (new) Eventuate Java client 0.2.0.RELEASE 2016-06-10 16:56:08 -07: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
Chris Richardson
f59f4c78dd Misc changes 2016-05-17 16:26:11 -07:00
Chris Richardson
4b3fe001e7 Changed to use the Eventuate Java Client
Simplified Spring MVC code since it supports CompletableFuture
2016-05-16 06:39:05 -07:00
dartpopikyardo
76f3c830af fix apigateway environment variables 2016-03-29 21:51:17 +03:00
dartpopikyardo
62d41e9d5b Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-03-29 20:30:22 +03:00
Andrew Revinsky (DART)
7748217973 Prebuild client app 2016-03-25 20:19:17 +03:00
dartpopikyardo
9a9511b2c4 changed POST /customers/{id}/toaccounts endpoint result format 2016-03-24 21:37:54 +03:00
Andrew Revinsky (DART)
28eefb81ab UI improvements 2016-03-24 03:17:27 +03:00
Andrew Revinsky (DART)
70b552a961 Sign in page after register, account create dialog dismissal, ref accounts - id undefined, default messages for empty lists (transfers & accounts). 2016-03-24 02:55:43 +03:00
Andrew Revinsky (DART)
2c5b5b4132 Merge commit 'e3c0112e8f85ff2ad4ac1155b0c94c487ea15a83' into wip-customer
* commit 'e3c0112e8f85ff2ad4ac1155b0c94c487ea15a83':
  changed POST /customers/{id}/toaccounts endpoint result format
  changed POST /customers/{id}/toaccounts endpoint result format
2016-03-23 22:15:22 +03:00
dartpopikyardo
e3c0112e8f changed POST /customers/{id}/toaccounts endpoint result format 2016-03-23 22:07:35 +03:00
dartpopikyardo
1a5442a060 changed POST /customers/{id}/toaccounts endpoint result format 2016-03-23 19:21:25 +03:00
Andrew Revinsky (DART)
0b81ae8c08 Sign in - revised error reporting 2016-03-23 01:42:42 +03:00
Andrew Revinsky (DART)
b6b1fb7f0f Adding 3rd Party Account - revised (#2) 2016-03-23 01:30:08 +03:00
Andrew Revinsky (DART)
e3dddcbc7b Merge commit 'e69922016248221d243e8cefa1b6fa813aa07eab' into wip-customer
* commit 'e69922016248221d243e8cefa1b6fa813aa07eab':
  revert
2016-03-23 01:15:41 +03:00
dartpopikyardo
e699220162 revert 2016-03-23 01:13:07 +03:00
Andrew Revinsky (DART)
fe89adba09 Merge commit 'a369c4989f8a238470e6ee7bd1a063f2937da1fc' into wip-customer
* commit 'a369c4989f8a238470e6ee7bd1a063f2937da1fc':
  changed return type in POST /customers/{id}/toaccounts endpoint
2016-03-23 00:57:57 +03:00
dartpopikyardo
a369c4989f changed return type in POST /customers/{id}/toaccounts endpoint 2016-03-23 00:57:09 +03:00
Andrew Revinsky (DART)
dd0bb4551b Merge commit 'bf01ad8e00ea68c884f287b89b25393fe2ef8c7c' into wip-customer
* commit 'bf01ad8e00ea68c884f287b89b25393fe2ef8c7c':
  changed return type in POST /customers/{id}/toaccounts endpoint
2016-03-23 00:23:08 +03:00
Andrew Revinsky (DART)
888544b700 Adding 3rd Party Account - revised 2016-03-23 00:22:52 +03:00
dartpopikyardo
bf01ad8e00 changed return type in POST /customers/{id}/toaccounts endpoint 2016-03-23 00:21:56 +03:00
Andrew Revinsky (DART)
faa4027305 Added timeago + correct refresh of the account page after transaction 2016-03-22 21:05:10 +03:00
Andrew Revinsky (DART)
d55ff55e96 Merge commit '9276b1bdc839d42b39f4bbc866e54835d4469ade' into wip-customer
* commit '9276b1bdc839d42b39f4bbc866e54835d4469ade':
  changed return result for empty accounts case
  fixed tests
  fixed tests
  added new fields to TransferDetails
  added new fields to TransferDetails
  -added tests for new endpoints
2016-03-22 19:51:49 +03:00
Andrew Revinsky (DART)
bdb4d3db26 Refactored reducers 2016-03-22 19:51:00 +03:00
dartpopikyardo
9276b1bdc8 changed return result for empty accounts case 2016-03-22 19:49:09 +03:00
dartpopikyardo
23fba9e462 fixed tests 2016-03-22 15:07:48 +03:00
dartpopikyardo
05ebfd7d14 fixed tests 2016-03-22 15:07:12 +03:00
dartpopikyardo
7b54b9042d added new fields to TransferDetails 2016-03-22 14:50:35 +03:00
dartpopikyardo
7c6328aa5e added new fields to TransferDetails 2016-03-22 14:46:03 +03:00
Andrew Revinsky (DART)
9967ad9c52 Transfer form - adjusted col widths for xs devices 2016-03-22 04:17:34 +03:00
Andrew Revinsky (DART)
8f86f72d85 Transfers (entire patch: list & make transfer) + Specific controls 2016-03-22 04:13:27 +03:00
dartpopikyardo
bdcdae862d -added tests for new endpoints 2016-03-22 01:09:39 +03:00
Andrew Revinsky (DART)
bfeb2e2e16 Add 3rd Party modal customers retrieval 2016-03-19 01:35:00 +03:00
Andrew Revinsky (DART)
b125b30304 Merge commit '40e03dd8e6d805a6f322c25861b3bebda306a947' into wip-customer
* commit '40e03dd8e6d805a6f322c25861b3bebda306a947':
  - updated swagger schema
  - added  GET /accounts/{accountId}/history   endpoint to get transactions history by accountId - updated swagger schema
  - added  GET /accounts/{accountId}/history   endpoint to get transactions history by accountId - updated swagger schema
2016-03-18 21:14:27 +03:00
Andrew Revinsky (DART)
febef227fd Add 3rd Party modal & Account page 2016-03-18 21:03:32 +03:00
dartpopikyardo
40e03dd8e6 - updated swagger schema 2016-03-18 17:37:50 +03:00
dartpopikyardo
63d0e21fc4 - added GET /accounts/{accountId}/history endpoint to get transactions history by accountId
- updated swagger schema
2016-03-18 17:35:18 +03:00
dartpopikyardo
6409ed1ab9 - added GET /accounts/{accountId}/history endpoint to get transactions history by accountId
- updated swagger schema
2016-03-18 17:33:28 +03:00
Andrew Revinsky (DART)
710487ebf9 Account Detail page 2016-03-18 04:20:46 +03:00
Andrew Revinsky (DART)
df0d391521 Merge commit '6d53f149c8c154330d26756d1de73f71d8662061' into wip-customer
* commit '6d53f149c8c154330d26756d1de73f71d8662061':
  - added field "details" to ToAccountInfo
2016-03-16 15:40:27 +03:00
dartpopikyardo
6d53f149c8 - added field "details" to ToAccountInfo 2016-03-16 15:35:59 +03:00
Andrew Revinsky (DART)
3a4ee4e90c Dialog dismissal, accounts fetching (stub) 2016-03-16 02:04:18 +03:00
Andrew Revinsky (DART)
5001127978 Merge commit 'b16039ee5377311aa60f9c71b0a7c1559836b61e' into wip-customer
* commit 'b16039ee5377311aa60f9c71b0a7c1559836b61e':
  - added title and description fields to accountInfo - added GET /accounts?customerId=XXX endpoint - refactored tests
2016-03-16 00:29:47 +03:00
Andrew Revinsky (DART)
ba5077685e Account creation dialog, tidy 2016-03-16 00:29:28 +03:00
dartpopikyardo
b16039ee53 - added title and description fields to accountInfo
- added GET /accounts?customerId=XXX endpoint
- refactored tests
2016-03-15 20:10:58 +03:00
Andrew Revinsky (DART)
c11f1b1a64 Authentication & user data retrieval fixed, API methods provided 2016-03-15 04:17:55 +03:00
Andrew Revinsky (DART)
f4ecc093fe Merge commit '28216a082dc6c6f83f960c412b222a9ade67aacf' into wip-customer
* commit '28216a082dc6c6f83f960c412b222a9ade67aacf':
  find current user by token
2016-03-12 02:53:48 +03:00
Andrew Revinsky (DART)
afa3cf0042 Sign In and Register actions 2016-03-12 02:53:37 +03:00
dartpopikyardo
28216a082d find current user by token 2016-03-11 21:10:50 +03:00
Andrew Revinsky (DART)
5511d1318e Merge commit '21d21b983384188ff2f6ae985caca26e17785087' into wip-customer
* commit '21d21b983384188ff2f6ae985caca26e17785087':
  - simplified customers tests - code clearings
  - updated swagger description - added api-gateway to docker-compose build
  - added api-gateway-service - fixed ObservableReturnValueHandler
2016-03-10 23:58:13 +03:00
Chris Richardson
c5778b1379 Added missing @EnableAutoConfiguration annotations 2016-03-07 17:52:56 -08:00
dartpopikyardo
21d21b9833 - simplified customers tests
- code clearings
2016-03-02 23:54:02 +03:00
dartpopikyardo
4bc8e8408d - updated swagger description
- added api-gateway to docker-compose build
2016-03-01 21:46:37 +03:00
dartpopikyardo
91361a1b18 - added api-gateway-service
- fixed ObservableReturnValueHandler
2016-02-29 21:41:55 +03:00
Andrew Revinsky (DART)
fb1069ebb9 Auth service and MyAccounts show data 2016-02-27 02:06:47 +03:00
Andrew Revinsky (DART)
e337f05c89 Merge commit '0a38ccd09e9d575a372d76597510bfd79502dbd9' into wip-customer
* commit '0a38ccd09e9d575a372d76597510bfd79502dbd9':
  added "/customers/{id}/toaccounts" andpoint test tests refactoring
2016-02-27 00:20:21 +03:00
Andrew Revinsky (DART)
725814407d Auth service receives login data 2016-02-27 00:20:07 +03:00
dartpopikyardo
0a38ccd09e added "/customers/{id}/toaccounts" andpoint test
tests refactoring
2016-02-25 22:35:36 +03:00
Andrew Revinsky (DART)
f887c9fefe Auth services added & user reducer 2016-02-25 21:11:38 +03:00
Andrew Revinsky (DART)
587a9164b7 Account Detail page + Modals 2016-02-20 22:55:10 +03:00
Andrew Revinsky (DART)
b07933121d Merge commit 'd1339729ec3e31d7bb5159640909d9839aa6a45f' into wip-customer
* commit 'd1339729ec3e31d7bb5159640909d9839aa6a45f':
  added simultaneous token-based and http basic authentication
  added simultaneous token-based and http basic authentication
2016-02-19 04:34:57 +03:00
Andrew Revinsky (DART)
98c01cceeb Landing page markup is complete, wiring - WIP 2016-02-19 04:34:30 +03:00
Main
d1339729ec added simultaneous token-based and http basic authentication 2016-02-18 23:57:39 +03:00
Main
99d07667bb added simultaneous token-based and http basic authentication 2016-02-18 13:16:43 +03:00
Main
d36c01b331 added simultaneous token-based and http basic authentication 2016-02-17 21:42:37 +03:00
Main
c61cc82d0a changed error format 2016-02-17 18:42:57 +03:00
Main
65524286da changed error format 2016-02-17 18:36:42 +03:00
Main
99000f04ed Merge remote-tracking branch 'remotes/dartandrevinsky/wip-customer' into wip-customer 2016-02-17 15:07:23 +03:00
Main
7b2fcc8a7c updated swagger info 2016-02-17 15:05:30 +03:00
Main
8615ac1d1e fixed authentication 2016-02-17 14:56:53 +03:00
Andrew Revinsky (DART)
8d1faedef3 Stylize main containers & other controls 2016-02-17 05:11:24 +03:00
Andrew Revinsky (DART)
84debbda28 Update prebuilt client 2016-02-17 00:29:00 +03:00
Andrew Revinsky (DART)
f74196e1f2 Update prebuilt client 2016-02-17 00:28:01 +03:00
Andrew Revinsky (DART)
a73fc2477b Merge commit 'f14fd73f6364ce6623a53074a24d8c49d93921de' into wip-customer
* commit 'f14fd73f6364ce6623a53074a24d8c49d93921de':
  temporarily removed authentication
  temporarily removed authentication
  temporarily removed authentication
2016-02-17 00:16:36 +03:00
Main
f14fd73f63 temporarily removed authentication 2016-02-17 00:14:16 +03:00
Andrew Revinsky (DART)
064151fa61 Integration with the Java server - using hashbangs 2016-02-17 00:12:57 +03:00
Main
43916e10aa temporarily removed authentication 2016-02-17 00:12:38 +03:00
Main
ffeb521e3b temporarily removed authentication 2016-02-16 23:57:02 +03:00
Main
1e2e564c29 fix default authorization page 2016-02-16 23:24:09 +03:00
Main
655f86071c fix static content load 2016-02-16 21:03:38 +03:00
Andrew Revinsky (DART)
ff00467b47 Integration with the Java server 2016-02-16 20:51:10 +03:00
Andrew Revinsky (DART)
28147a707d Merge commit 'e8e147e3ebb0704aff75e4227c2224f7946a681b' into wip-customer
* commit 'e8e147e3ebb0704aff75e4227c2224f7946a681b':
  - added copy static content task
2016-02-16 20:29:47 +03:00
Main
e8e147e3eb - added copy static content task 2016-02-16 20:26:11 +03:00
Andrew Revinsky (DART)
8228738710 Merge commit '8c342e40ce02aa3c3d06ecec3c6c84c1388ef7a1' into wip-customer
* commit '8c342e40ce02aa3c3d06ecec3c6c84c1388ef7a1':
  - added /login endpoint - added authorization test
  Running Docker using Vagrant
2016-02-16 19:59:28 +03:00
Main
8c342e40ce - added /login endpoint
- added authorization test
2016-02-15 23:18:28 +03:00
Andrew Revinsky (DART)
42db215414 Fully functional Sign Up & Sign Incontrols. 2016-02-15 21:58:46 +03:00
Main
23ffa96f47 Merge remote-tracking branches 'remotes/upstream/wip-customer' and 'remotes/upstream/wip-vagrant' into wip-customer 2016-02-15 21:26:53 +03:00
Andrew Revinsky (DART)
822c0efb6d Merge commit 'a217f1b1e6ba53fc90c12507672395ca20920672'
* commit 'a217f1b1e6ba53fc90c12507672395ca20920672':
  Enhanced curl commands Fixed SpringFox/SwaggerUI issue with CustomerController
  Ignore test failures when EVENTUATE_API_KEY_ID etc is not set
  Ignore test failures when EVENTUATE_API_KEY_ID etc is not set
  - updated e2e-tests - added new services to docker configuration
2016-02-15 17:15:28 +03:00
Andrew Revinsky (DART)
5108bc1b27 Prettied UI for the auth controls 2016-02-15 17:09:49 +03:00
Main
8564ec3fb2 Merge remote-tracking branch 'remotes/upstream/master' 2016-02-15 10:27:55 +03:00
Chris Richardson
a217f1b1e6 Enhanced curl commands
Fixed SpringFox/SwaggerUI issue with CustomerController
2016-02-12 17:55:20 -08:00
Chris Richardson
92526f2780 Ignore test failures when EVENTUATE_API_KEY_ID etc is not set 2016-02-12 16:54:18 -08:00
Chris Richardson
8ab13e580b Merge branch 'master' into wip-customer 2016-02-12 16:46:07 -08:00
Main
a095076c0c - updated e2e-tests
- added new services to docker configuration
2016-02-12 22:48:23 +03:00
Andrew Revinsky (DART)
fc2bd9970b prebuilt web app updated 2016-02-12 22:01:58 +03:00
Andrew Revinsky (DART)
a5994ad496 Merge commit '5c85418cc43318eb2a9c3fa1a4c94c124a17676c'
* commit '5c85418cc43318eb2a9c3fa1a4c94c124a17676c':
  - completed moving "toAccounts" from List to Map - created CustomerEvent superclass - removed redundant CustomersNotFoundException - added integration tests for customers
  refactoring
  - updated swagger description - changed customer registration endpoint - updated tests
  - updated swagger description - added /customers/{id}/toaccounts endpoint
  updated swagger description
  removed customerId from Account aggregate
  added customers-query-side part
  added customer-command-side
  fixed fields names added swagger  definition for new endpoints
  added customer backend module added auth module added customerId to Account aggregate
2016-02-12 21:59:58 +03:00
Andrew Revinsky (DART)
bfd04ef726 Changed build process, with auth (login, registration, and couunt) components and containers 2016-02-12 21:59:24 +03:00
Main
5c85418cc4 - completed moving "toAccounts" from List to Map
- created CustomerEvent superclass
- removed redundant CustomersNotFoundException
- added integration tests for customers
2016-02-11 21:47:48 +03:00
Main
c0a9d6ed7d refactoring 2016-02-10 23:28:35 +03:00
Main
92d0940222 - updated swagger description
- changed customer registration endpoint
- updated tests
2016-02-09 22:17:27 +03:00
Andrew Revinsky
7f1f2af188 Adding the frontend project as files 2016-02-08 23:17:37 +03:00
Andrew Revinsky
51e9d6c7fa Removing the remote tree part 2016-02-08 23:16:51 +03:00
Main
cd35ac3d31 - updated swagger description
- added /customers/{id}/toaccounts endpoint
2016-02-08 22:54:03 +03:00
Andrew Revinsky
5d7e56578f Add client app folder 2016-02-08 21:41:35 +03:00
Main
59ecaa804a updated swagger description 2016-02-08 13:25:09 +03:00
Main
06f14a54bf removed customerId from Account aggregate
added tests to customers-query-side and customers-command-side
2016-02-05 20:11:53 +03:00
Main
afb7c9bc49 added customers-query-side part
added service wrappers to customers-command-side and customers-query-side
2016-02-05 01:27:17 +03:00
dartpopikyardo
e0691a61a2 added customer-command-side 2016-02-03 23:04:15 +03:00
dartpopikyardo
cbee50658e fixed fields names
added swagger  definition for new endpoints
2016-02-03 21:03:52 +03:00
dartpopikyardo
85f8826741 added customer backend module
added auth module
added customerId to Account aggregate
2016-02-02 19:16:29 +03:00
312 changed files with 76028 additions and 911 deletions

6
.gitignore vendored
View File

@@ -25,6 +25,12 @@ browserapp/reports
loginapp/reports
local_developer_config
js-frontend/node_modules
js-frontend/build
js-frontend/dist
js-frontend/dist-intermediate
/web/web.log
*.log

View File

@@ -1,7 +1,7 @@
#Event-Sourcing+CQRS example application
This example application is the money transfer application described in my talk [Building and deploying microservices with event sourcing, CQRS and Docker](http://plainoldobjects.com/presentations/building-and-deploying-microservices-with-event-sourcing-cqrs-and-docker/).
This talk describe a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
This talk describes a way of architecting highly scalable and available applications that is based on microservices, polyglot persistence,
event sourcing (ES) and command query responsibility segregation (CQRS).
Applications consist of loosely coupled components that communicate using events.
These components can be deployed either as separate services or packaged as a monolithic application for simplified development and testing.
@@ -94,9 +94,12 @@ First, you need to tell the query side code how to connect to MongoDB:
```
[Docker Compose](https://docs.docker.com/compose/) is a great way to run MongoDB.
You can run the `docker-compose up -d mongodb` to run MongoDB.
You can run the `docker-compose up -d mongodb` to run MongoDB and then set `SPRING_DATA_MONGODB_URI` as follows:
```
export SPRING_DATA_MONGODB_URI=mongodb://$(docker-machine ip default)/yourdb
```
Second, some of the tests in accounts-command-side-service, transactions-command-side-service, accounts-query-side-service and e2e-test need you need to set some environment variables that tell them how to connect to the Event Store server.
Second, some of the tests in accounts-command-side-service, transactions-command-side-service, accounts-query-side-service and e2e-test require you to set some environment variables that tell them how to connect to the Event Store server.
But don't worry.
The build is configured to ignore failures for those projects.
@@ -117,6 +120,15 @@ Simply use this command:
java -jar monolithic-service/build/libs/monolithic-service.jar
```
This will start the service running on port 8080 (you can change using the --server.port=9999 option).
Once the service has started you can open the Swagger UI: http://localhost:8080/swagger-ui.html.
You can then:
1. Create two accounts (save the account ids)
2. Create a money transfer
3. View the updated account balances
## Running the microservices
The other option is to run the services separately.

View File

@@ -6,6 +6,12 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DOCKER_COMPOSE="docker-compose -p event-sourcing-examples"
if [ "$1" = "-f" ] ; then
shift;
DOCKER_COMPOSE="$DOCKER_COMPOSE -f ${1?}"
shift
fi
if [ "$1" = "--use-existing" ] ; then
shift;
else
@@ -13,6 +19,13 @@ else
${DOCKER_COMPOSE?} rm -v --force
fi
NO_RM=false
if [ "$1" = "--no-rm" ] ; then
NO_RM=true
shift
fi
${DOCKER_COMPOSE?} up -d mongodb
if [ -z "$DOCKER_HOST_IP" ] ; then
@@ -47,5 +60,7 @@ set -e
./gradlew -a $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false
${DOCKER_COMPOSE?} stop
${DOCKER_COMPOSE?} rm -v --force
if [ $NO_RM = false ] ; then
${DOCKER_COMPOSE?} stop
${DOCKER_COMPOSE?} rm -v --force
fi

View File

@@ -7,19 +7,19 @@ This application consists of three microservices:
* Account Service - the command side business logic for Accounts
* Money Transfer Service - the command side business logic for Money Transfers
* Query service - query side implementation of a MongoDB-based, denormalized view of Accounts and MoneyTransfers
The Account Service consists of the following modules:
* accounts-command-side-backend - the Account aggregate
* accounts-command-side-web - a REST API for creating and retrieving Accounts
* accounts-command-side-service - a standalone microservice
The Money Transfer Service consists of the following modules:
* transactions-command-side-backend - the MoneyTransfer aggregate
* transactions-command-side-web - a REST API for creating and retrieving Money Transfers
* transactions-command-side-service - a standalone microservice
The Query Service consists the following modules:
* accounts-query-side-backend - MongoDB-based, denormalized view of Accounts and MoneyTransfers
@@ -28,10 +28,8 @@ The Query Service consists the following modules:
# Deploying the application
These services can be deployed either as either separate standalone services using the Event Store server, or they can be deployed as a monolithic application for simpified integration testing.
These services can be deployed either as either separate standalone services using the Event Store server, or they can be deployed as a monolithic application for simplified integration testing.
The three services can also be packaged as a single monolithic web application in order to be used with the embedded Event Store:
* monolithic-service - all-in-one, monolithic packaging of the application

View File

@@ -3,12 +3,12 @@ apply plugin: 'java'
dependencies {
compile project(":common-backend")
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,8 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.Event;
import net.chrisrichardson.eventstore.EventUtil;
import net.chrisrichardson.eventstore.ReflectiveMutableCommandProcessingAggregate;
import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitFailedDueToInsufficientFundsEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
@@ -16,7 +16,7 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
private BigDecimal balance;
public List<Event> process(OpenAccountCommand cmd) {
return EventUtil.events(new AccountOpenedEvent(cmd.getInitialBalance()));
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription()));
}
public List<Event> process(DebitAccountCommand cmd) {

View File

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

View File

@@ -1,13 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.EventStore;
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
import net.chrisrichardson.eventstore.repository.AggregateRepository;
import io.eventuate.AggregateRepository;
import io.eventuate.EventuateAggregateStore;
import io.eventuate.javaclient.spring.EnableEventHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableJavaEventHandlers
@EnableEventHandlers
public class AccountConfiguration {
@Bean
@@ -22,7 +22,7 @@ public class AccountConfiguration {
}
@Bean
public AggregateRepository<Account, AccountCommand> accountRepository(EventStore eventStore) {
public AggregateRepository<Account, AccountCommand> accountRepository(EventuateAggregateStore eventStore) {
return new AggregateRepository<Account, AccountCommand>(Account.class, eventStore);
}

View File

@@ -1,10 +1,11 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
import net.chrisrichardson.eventstore.repository.AggregateRepository;
import io.eventuate.AggregateRepository;
import io.eventuate.EntityWithIdAndVersion;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public class AccountService {
@@ -14,8 +15,8 @@ public class AccountService {
this.accountRepository = accountRepository;
}
public rx.Observable<EntityWithIdAndVersion<Account>> openAccount(BigDecimal initialBalance) {
return accountRepository.save(new OpenAccountCommand(initialBalance));
public CompletableFuture<EntityWithIdAndVersion<Account>> openAccount(String customerId, String title, BigDecimal initialBalance, String description) {
return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance, description));
}
}

View File

@@ -1,36 +1,50 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.javaapi.consumer.EventHandlerContext;
import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventHandlerContext;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
import net.chrisrichardson.eventstore.subscriptions.*;
import rx.Observable;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
@EventSubscriber(id="accountEventHandlers")
public class AccountWorkflow implements CompoundEventHandler {
@EventSubscriber(id = "accountEventHandlers")
public class AccountWorkflow {
@EventHandlerMethod
public Observable<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) {
public CompletableFuture<?> debitAccount(EventHandlerContext<MoneyTransferCreatedEvent> ctx) {
MoneyTransferCreatedEvent event = ctx.getEvent();
BigDecimal amount = event.getDetails().getAmount();
EntityIdentifier transactionId = ctx.getEntityIdentifier();
String transactionId = ctx.getEntityId();
EntityIdentifier fromAccountId = event.getDetails().getFromAccountId();
String fromAccountId = event.getDetails().getFromAccountId();
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId));
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
}
@EventHandlerMethod
public Observable<?> creditAccount(EventHandlerContext<DebitRecordedEvent> ctx) {
public CompletableFuture<EntityWithIdAndVersion<Account>> creditAccount(EventHandlerContext<DebitRecordedEvent> ctx) {
DebitRecordedEvent event = ctx.getEvent();
BigDecimal amount = event.getDetails().getAmount();
EntityIdentifier fromAccountId = event.getDetails().getToAccountId();
EntityIdentifier transactionId = ctx.getEntityIdentifier();
String fromAccountId = event.getDetails().getToAccountId();
String transactionId = ctx.getEntityId();
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId));
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
}
}

View File

@@ -1,15 +1,15 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import io.eventuate.Aggregate;
import java.math.BigDecimal;
public class CreditAccountCommand implements AccountCommand {
private final BigDecimal amount;
private final EntityIdentifier transactionId;
private final String transactionId;
public CreditAccountCommand(BigDecimal amount, EntityIdentifier transactionId) {
public CreditAccountCommand(BigDecimal amount, String transactionId) {
this.amount = amount;
this.transactionId = transactionId;
@@ -19,7 +19,7 @@ public class CreditAccountCommand implements AccountCommand {
return amount;
}
public EntityIdentifier getTransactionId() {
public String getTransactionId() {
return transactionId;
}
}

View File

@@ -1,15 +1,12 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import java.math.BigDecimal;
public class DebitAccountCommand implements AccountCommand {
private final BigDecimal amount;
private final EntityIdentifier transactionId;
private final String transactionId;
public DebitAccountCommand(BigDecimal amount, EntityIdentifier transactionId) {
public DebitAccountCommand(BigDecimal amount, String transactionId) {
this.amount = amount;
this.transactionId = transactionId;
@@ -19,7 +16,7 @@ public class DebitAccountCommand implements AccountCommand {
return amount;
}
public EntityIdentifier getTransactionId() {
public String getTransactionId() {
return transactionId;
}
}

View File

@@ -5,13 +5,31 @@ import java.math.BigDecimal;
public class OpenAccountCommand implements AccountCommand {
private String customerId;
private String title;
private BigDecimal initialBalance;
private String description;
public OpenAccountCommand(BigDecimal initialBalance) {
public OpenAccountCommand(String customerId, String title, BigDecimal initialBalance, String description) {
this.customerId = customerId;
this.title = title;
this.initialBalance = initialBalance;
this.description = description;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
}

View File

@@ -1,7 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
import net.chrisrichardson.eventstore.CommandProcessingAggregates;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import org.junit.Assert;
import org.junit.Test;
@@ -14,14 +13,16 @@ public class AccountTest {
@Test
public void testSomething() {
Account account = new Account();
String title = "My Account";
String customerId = "00000000-00000000";
BigDecimal initialBalance = new BigDecimal(512);
List<Event> events = CommandProcessingAggregates.processToList(account, (AccountCommand)new OpenAccountCommand(initialBalance));
List<Event> events = account.process(new OpenAccountCommand(customerId, title, initialBalance, ""));
Assert.assertEquals(1, events.size());
Assert.assertEquals(AccountOpenedEvent.class, events.get(0).getClass());
account.applyEvent(events.get(0));
Assert.assertEquals(initialBalance, account.getBalance());
}
}

View File

@@ -1,5 +1,6 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
@@ -10,9 +11,6 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
}

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CommandSideWebAccountsConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -13,7 +13,7 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({CommandSideWebAccountsConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
@Import({CommandSideWebAccountsConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class AccountsCommandSideServiceConfiguration {

View File

@@ -1,7 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts.CreateAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,7 +25,7 @@ public class AccountsCommandSideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path;
return "http://localhost:" + port + "/api" + path;
}
@Autowired
@@ -37,11 +37,13 @@ public class AccountsCommandSideServiceIntegrationTest {
BigDecimal initialFromAccountBalance = new BigDecimal(500);
BigDecimal initialToAccountBalance = new BigDecimal(100);
BigDecimal amountToTransfer = new BigDecimal(150);
String customerId = "00000000-00000000";
String title = "My Account";
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialFromAccountBalance), CreateAccountResponse.class).getBody();
final CreateAccountResponse fromAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, "", initialFromAccountBalance), CreateAccountResponse.class).getBody();
final String fromAccountId = fromAccount.getAccountId();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(initialToAccountBalance), CreateAccountResponse.class).getBody();
CreateAccountResponse toAccount = restTemplate.postForEntity(baseUrl("/accounts"), new CreateAccountRequest(customerId, title, "", initialToAccountBalance), CreateAccountResponse.class).getBody();
String toAccountId = toAccount.getAccountId();
Assert.assertNotNull(fromAccountId);

View File

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

View File

@@ -1,16 +1,19 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import rx.Observable;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/accounts")
@RequestMapping("/api/accounts")
public class AccountController {
private AccountService accountService;
@@ -21,8 +24,8 @@ public class AccountController {
}
@RequestMapping(method = RequestMethod.POST)
public Observable<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
return accountService.openAccount(request.getInitialBalance())
.map(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityIdentifier().getId()));
public CompletableFuture<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
return accountService.openAccount(request.getCustomerId(), request.getTitle(), request.getInitialBalance(), request.getDescription())
.thenApply(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityId()));
}
}

View File

@@ -1,36 +1,16 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Bean;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@Configuration
@Import({AccountConfiguration.class})
@ComponentScan
public class CommandSideWebAccountsConfiguration extends WebMvcConfigurerAdapter {
@EnableAutoConfiguration
public class CommandSideWebAccountsConfiguration {
class FakeThing {}
@Bean
public FakeThing init(RequestMappingHandlerAdapter adapter) {
// https://jira.spring.io/browse/SPR-13083
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
handlers.add(0, new ObservableReturnValueHandler());
adapter.setReturnValueHandlers(handlers);
return new FakeThing();
}
}

View File

@@ -1,29 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.accounts;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.DecimalMin;
import java.math.BigDecimal;
public class CreateAccountRequest {
@NotNull
@DecimalMin("0")
private BigDecimal initialBalance;
public CreateAccountRequest() {
}
public CreateAccountRequest(BigDecimal initialBalance) {
this.initialBalance = initialBalance;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}
public void setInitialBalance(BigDecimal initialBalance) {
this.initialBalance = initialBalance;
}
}

View File

@@ -35,16 +35,16 @@ public class AccountControllerIntegrationTest {
@Test
public void shouldCreateAccount() throws Exception {
mockMvc.perform(post("/accounts")
mockMvc.perform(post("/api/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"initialBalance\" : 500}")
.content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
@Test
public void shouldRejectBadRequest() throws Exception {
mockMvc.perform(post("/accounts")
mockMvc.perform(post("/api/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"initialBalanceXXX\" : 500}")
.accept(MediaType.APPLICATION_JSON))

View File

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

View File

@@ -3,17 +3,13 @@ apply plugin: 'java'
dependencies {
compile project(":common-backend")
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
compile 'com.fasterxml.jackson.core:jackson-core:2.4.3'
compile 'com.fasterxml.jackson.core:jackson-databind:2.4.3'
compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:2.4.3'
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,18 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
public class AccountChangeInfo {
private String changeId;
private String transactionId;
private String transactionType;
private long amount;
private long balanceDelta;
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this.changeId = changeId;
this.transactionId = transactionId;
this.transactionType = transactionType;
this.amount = amount;
this.balanceDelta = balanceDelta;
}
}

View File

@@ -1,6 +1,10 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import java.util.List;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import java.util.*;
/**
* Created by cer on 11/21/14.
@@ -8,40 +12,77 @@ import java.util.List;
public class AccountInfo {
private String id;
private String customerId;
private String title;
private String description;
private long balance;
private List<AccountChangeInfo> changes;
private List<AccountTransactionInfo> transactions;
private Map<String, AccountTransactionInfo> transactions;
private Map<String, TransferState> transferStates;
private String version;
private Date date;
private AccountInfo() {
}
public AccountInfo(String id, long balance, List<AccountChangeInfo> changes, List<AccountTransactionInfo> transactions, String version) {
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version) {
this(id, customerId, title, description, balance, changes, transactions, version, new Date());
}
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version, Date date) {
this.id = id;
this.customerId = customerId;
this.title = title;
this.description = description;
this.balance = balance;
this.changes = changes;
this.transactions = transactions;
this.version = version;
this.date = date;
}
public String getId() {
return id;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public long getBalance() {
return balance;
}
public List<AccountChangeInfo> getChanges() {
return changes;
return changes == null ? Collections.EMPTY_LIST : changes;
}
public List<AccountTransactionInfo> getTransactions() {
return transactions;
return transactions == null ? Collections.EMPTY_LIST : new ArrayList<>(transactions.values());
}
public String getVersion() {
return version;
}
public Date getDate() {
return date;
}
public Map<String, TransferState> getTransferStates() {
return transferStates;
}
public void setTransferStates(Map<String, TransferState> transferStates) {
this.transferStates = transferStates;
}
}

View File

@@ -2,5 +2,9 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.ac
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
interface AccountInfoRepository extends MongoRepository<AccountInfo, String> {
List<AccountInfo> findByCustomerId(String customerId);
}

View File

@@ -1,19 +1,23 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import com.mongodb.WriteResult;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
import static org.springframework.data.mongodb.core.query.Criteria.where;
public class AccountInfoUpdateService {
public class AccountInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass());
private AccountInfoRepository accountInfoRepository;
@@ -25,29 +29,35 @@ public class AccountInfoUpdateService {
}
public void create(String accountId, BigDecimal initialBalance, String version) {
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String description, String version) {
try {
accountInfoRepository.save(new AccountInfo(
accountId,
toIntegerRepr(initialBalance),
Collections.<AccountChangeInfo>emptyList(),
Collections.<AccountTransactionInfo>emptyList(),
version));
AccountChangeInfo ci = new AccountChangeInfo();
ci.setAmount(toIntegerRepr(initialBalance));
WriteResult x = mongoTemplate.upsert(new Query(where("id").is(accountId).and("version").exists(false)),
new Update()
.set("customerId", customerId)
.set("title", title)
.set("description", description)
.set("balance", toIntegerRepr(initialBalance))
.push("changes", ci)
.set("date", new Date())
.set("version", version),
AccountInfo.class);
logger.info("Saved in mongo");
} catch (DuplicateKeyException t) {
logger.warn("When saving ", t);
} catch (Throwable t) {
logger.error("Error during saving: ");
logger.error("Error during saving: ", t);
throw new RuntimeException(t);
}
}
public void addTransaction(String eventId, String fromAccountId, AccountTransactionInfo ti) {
mongoTemplate.updateMulti(new Query(where("id").is(fromAccountId).and("version").lt(eventId)),
public void addTransaction(String accountId, AccountTransactionInfo ti) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),
new Update().
push("transactions", ti).
set("version", eventId),
set("transactions." + ti.getTransactionId(), ti),
AccountInfo.class);
}
@@ -61,5 +71,10 @@ public class AccountInfoUpdateService {
AccountInfo.class);
}
public void updateTransactionStatus(String accountId, String transactionId, TransferState status) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),
new Update().
set("transferStates." + transactionId, status),
AccountInfo.class);
}
}

View File

@@ -1,9 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.EntityNotFoundException;
import rx.Observable;
import java.util.List;
public class AccountQueryService {
@@ -13,11 +10,17 @@ public class AccountQueryService {
this.accountInfoRepository = accountInfoRepository;
}
public Observable<AccountInfo> findByAccountId(EntityIdentifier accountId) {
AccountInfo account = accountInfoRepository.findOne(accountId.getId());
public AccountInfo findByAccountId(String accountId) {
AccountInfo account = accountInfoRepository.findOne(accountId);
if (account == null)
return Observable.error(new AccountNotFoundException(accountId.getId()));
throw new AccountNotFoundException(accountId);
else
return Observable.just(account);
if(account.getTransferStates()!=null)
account.getTransactions().stream().forEach(ati -> ati.setStatus(account.getTransferStates().get(ati.getTransactionId())));
return account;
}
public List<AccountInfo> findByCustomerId(String customerId) {
return accountInfoRepository.findByCustomerId(customerId);
}
}

View File

@@ -1,17 +1,16 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountChangedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import io.eventuate.DispatchedEvent;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.CreditRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.FailedDebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
import net.chrisrichardson.eventstore.subscriptions.CompoundEventHandler;
import net.chrisrichardson.eventstore.subscriptions.DispatchedEvent;
import net.chrisrichardson.eventstore.subscriptions.EventHandlerMethod;
import net.chrisrichardson.eventstore.subscriptions.EventSubscriber;
import rx.Observable;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -19,9 +18,9 @@ import java.math.BigDecimal;
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
@EventSubscriber(id="querySideEventHandlers")
public class AccountQueryWorkflow implements CompoundEventHandler {
@EventSubscriber(id="querySideEventHandlers")
public class AccountQueryWorkflow {
private Logger logger = LoggerFactory.getLogger(getClass());
private AccountInfoUpdateService accountInfoUpdateService;
@@ -31,59 +30,76 @@ public class AccountQueryWorkflow implements CompoundEventHandler {
}
@EventHandlerMethod
public Observable<Object> create(DispatchedEvent<AccountOpenedEvent> de) {
AccountOpenedEvent event = de.event();
String id = de.getEntityIdentifier().getId();
String eventId = de.eventId().asString();
public void create(DispatchedEvent<AccountOpenedEvent> de) {
AccountOpenedEvent event = de.getEvent();
String id = de.getEntityId();
String eventId = de.getEventId().asString();
logger.info("**************** account version=" + id + ", " + eventId);
BigDecimal initialBalance = event.getInitialBalance();
accountInfoUpdateService.create(id, initialBalance, eventId);
return Observable.just(null);
String customerId = event.getCustomerId();
String title = event.getTitle();
String description = event.getDescription();
accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId);
}
@EventHandlerMethod
public Observable<Object> recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
String eventId = de.eventId().asString();
String moneyTransferId = de.getEntityIdentifier().getId();
String fromAccountId = de.event().getDetails().getFromAccountId().getId();
String toAccountId = de.event().getDetails().getToAccountId().getId();
logger.info("**************** account version=" + fromAccountId + ", " + de.eventId().asString());
logger.info("**************** account version=" + toAccountId + ", " + de.eventId().asString());
public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
String eventId = de.getEventId().asString();
String moneyTransferId = de.getEntityId();
String fromAccountId = de.getEvent().getDetails().getFromAccountId();
String toAccountId = de.getEvent().getDetails().getToAccountId();
logger.info("**************** account version=" + fromAccountId + ", " + de.getEventId().asString());
logger.info("**************** account version=" + toAccountId + ", " + de.getEventId().asString());
AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId, fromAccountId, toAccountId, toIntegerRepr(de.event().getDetails().getAmount()));
accountInfoUpdateService.addTransaction(eventId, fromAccountId, ti);
accountInfoUpdateService.addTransaction(eventId, toAccountId, ti);
return Observable.just(null);
AccountTransactionInfo ti = new AccountTransactionInfo(moneyTransferId,
fromAccountId,
toAccountId,
toIntegerRepr(de.getEvent().getDetails().getAmount()),
de.getEvent().getDetails().getDate(),
de.getEvent().getDetails().getDescription());
accountInfoUpdateService.addTransaction(fromAccountId, ti);
accountInfoUpdateService.addTransaction(toAccountId, ti);
}
@EventHandlerMethod
public Observable<Object> recordDebit(DispatchedEvent<AccountDebitedEvent> de) {
return saveChange(de, -1);
public void recordDebit(DispatchedEvent<AccountDebitedEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.DEBITED);
saveChange(de, -1);
}
@EventHandlerMethod
public Observable<Object> recordCredit(DispatchedEvent<AccountCreditedEvent> de) {
return saveChange(de, +1);
public void recordCredit(DispatchedEvent<AccountCreditedEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
saveChange(de, +1);
}
public <T extends AccountChangedEvent> Observable<Object> saveChange(DispatchedEvent<T> de, int delta) {
String changeId = de.eventId().asString();
String transactionId = de.event().getTransactionId().getId();
long amount = toIntegerRepr(de.event().getAmount());
@EventHandlerMethod
public void recordFailed(DispatchedEvent<AccountDebitFailedDueToInsufficientFundsEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS);
}
public <T extends AccountChangedEvent> void saveChange(DispatchedEvent<T> de, int delta) {
String changeId = de.getEventId().asString();
String transactionId = de.getEvent().getTransactionId();
long amount = toIntegerRepr(de.getEvent().getAmount());
long balanceDelta = amount * delta;
AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.event().getClass().getSimpleName(), amount, balanceDelta);
String accountId = de.getEntityIdentifier().getId();
logger.info("**************** account version=" + accountId + ", " + de.eventId().asString());
AccountChangeInfo ci = new AccountChangeInfo(changeId, transactionId, de.getEvent().getClass().getSimpleName(), amount, balanceDelta);
String accountId = de.getEntityId();
logger.info("**************** account version=" + accountId + ", " + de.getEventId().asString());
accountInfoUpdateService.updateBalance(accountId, changeId, balanceDelta, ci);
return Observable.just(null);
}
}

View File

@@ -1,16 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
public class AccountTransactionInfo {
private String transactionId;
private String fromAccountId;
private String toAccountId;
private long amount;
public AccountTransactionInfo(String transactionId, String fromAccountId, String toAccountId, long amount) {
this.transactionId = transactionId;
this.fromAccountId = fromAccountId;
this.toAccountId = toAccountId;
this.amount = amount;
}
}

View File

@@ -1,7 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import net.chrisrichardson.eventstore.javaapi.consumer.EnableJavaEventHandlers;
import io.eventuate.javaclient.spring.EnableEventHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
@@ -9,7 +9,7 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie
@Configuration
@EnableMongoRepositories
@EnableJavaEventHandlers
@EnableEventHandlers
public class QuerySideAccountConfiguration {
@Bean
@@ -27,8 +27,6 @@ public class QuerySideAccountConfiguration {
return new AccountQueryService(accountInfoRepository);
}
@Bean
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
return new QuerySideDependencyChecker(mongoTemplate);

View File

@@ -3,8 +3,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.ac
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import rx.Observable;
import rx.Subscriber;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@@ -22,17 +20,7 @@ public class QuerySideDependencyChecker {
try {
logger.info("Checking mongodb connectivity {}", System.getenv("SPRING_DATA_MONGODB_URI"));
Observable.<Object>create(new Observable.OnSubscribe<Object>() {
@Override
public void call(Subscriber<? super Object> subscriber) {
try {
subscriber.onNext(mongoTemplate.getDb().getCollectionNames());
subscriber.onCompleted();
} catch (Throwable t) {
subscriber.onError(t);
}
}
}).timeout(5, TimeUnit.SECONDS).toBlocking().first();
mongoTemplate.getDb().getCollectionNames();
} catch (Throwable e) {
throw new RuntimeException("Error connecting to Mongo - have you set SPRING_DATA_MONGODB_URI or --spring.data.mongodb_uri?", e);

View File

@@ -0,0 +1,149 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import io.eventuate.javaclient.spring.jdbc.IdGenerator;
import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = AccountInfoUpdateServiceTest.AccountInfoUpdateServiceTestConfiguration.class)
@IntegrationTest
public class AccountInfoUpdateServiceTest {
@Configuration
@EnableAutoConfiguration
@Import({QuerySideAccountConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public static class AccountInfoUpdateServiceTestConfiguration {
}
@Autowired
private AccountInfoUpdateService accountInfoUpdateService;
@Autowired
private AccountQueryService accountQueryService;
@Test
public void shouldSaveAccountInfo() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertEquals(customerId, accountInfo.getCustomerId());
assertEquals(title, accountInfo.getTitle());
assertEquals(description, accountInfo.getDescription());
assertEquals(initialBalance.longValue() * 100, accountInfo.getBalance());
assertEquals(1, accountInfo.getChanges().size());
assertTrue(accountInfo.getTransactions().isEmpty());
assertEquals(version, accountInfo.getVersion());
String changeId = x.genId().asString();
String transactionId = x.genId().asString();
AccountChangeInfo change = new AccountChangeInfo(changeId, transactionId, AccountCreditedEvent.class.getSimpleName(),
500, +1);
accountInfoUpdateService.updateBalance(accountId, changeId, 500,
change);
accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(initialBalance.add(new BigDecimal(5)).longValue() * 100, accountInfo.getBalance());
assertFalse(accountInfo.getChanges().isEmpty());
assertEquals(change, accountInfo.getChanges().get(1));
String eventId = x.genId().asString();
AccountTransactionInfo ti = new AccountTransactionInfo(transactionId, accountId, accountId, 5, new Date(), "A transfer");
accountInfoUpdateService.addTransaction(accountId, ti);
accountInfo = accountQueryService.findByAccountId(accountId);
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(ti, accountInfo.getTransactions().get(0));
}
@Test
public void shouldHandleDuplicateSaveAccountInfo() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
}
@Test
public void shouldUpdateTransactionStatus() {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
String transactionId = x.genId().asString();
AccountTransactionInfo transactionInfo = new AccountTransactionInfo();
transactionInfo.setTransactionId(transactionId);
transactionInfo.setStatus(TransferState.INITIAL);
accountInfoUpdateService.addTransaction(accountId, transactionInfo);
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(1, accountInfo.getTransactions().size());
assertEquals(TransferState.INITIAL, accountInfo.getTransactions().get(0).getStatus());
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(1, accountInfo.getTransactions().size());
assertEquals(TransferState.COMPLETED, accountInfo.getTransactions().get(0).getStatus());
}
}

View File

@@ -1,5 +1,6 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
@@ -10,8 +11,6 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "net.chrisrichardson.eventstore.client:eventstore-http-stomp-client_2.10:$eventStoreClientVersion"
testCompile project(":testutil")
testCompile "org.springframework.boot:spring-boot-starter-test"

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.client.config.EventStoreHttpClientConfiguration;
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.QuerySideWebConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -13,7 +13,7 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({QuerySideWebConfiguration.class, EventStoreHttpClientConfiguration.class, CommonSwaggerConfiguration.class})
@Import({QuerySideWebConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class AccountsQuerySideServiceConfiguration {

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts.GetAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.GetAccountResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -13,10 +13,10 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import rx.Observable;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@@ -30,7 +30,7 @@ public class AccountsQuerySideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/" + path;
return "http://localhost:" + port + "/api" + path;
}
@Autowired
@@ -51,8 +51,8 @@ public class AccountsQuerySideServiceIntegrationTest {
eventually(
new Producer<GetAccountResponse>() {
@Override
public Observable<GetAccountResponse> produce() {
return Observable.just(restTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId), GetAccountResponse.class).getBody());
public CompletableFuture<GetAccountResponse> produce() {
return CompletableFuture.completedFuture(restTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId), GetAccountResponse.class).getBody());
}
},
new Verifier<GetAccountResponse>() {

View File

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

View File

@@ -1,36 +1,14 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.QuerySideAccountConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.util.ObservableReturnValueHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Bean;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@Configuration
@Import({QuerySideAccountConfiguration.class})
@ComponentScan
public class QuerySideWebConfiguration extends WebMvcConfigurerAdapter {
public class QuerySideWebConfiguration {
class FakeThing {}
@Bean
public FakeThing init(RequestMappingHandlerAdapter adapter) {
// https://jira.spring.io/browse/SPR-13083
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>(adapter.getReturnValueHandlers());
handlers.add(0, new ObservableReturnValueHandler());
adapter.setReturnValueHandlers(handlers);
return new FakeThing();
}
}

View File

@@ -1,16 +1,22 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.accounts;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountNotFoundException;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountQueryService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import rx.Observable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class AccountQueryController {
private AccountQueryService accountInfoQueryService;
@@ -20,17 +26,40 @@ public class AccountQueryController {
this.accountInfoQueryService = accountInfoQueryService;
}
@RequestMapping(value="/accounts/{accountId}", method = RequestMethod.GET)
public Observable<GetAccountResponse> get(@PathVariable String accountId) {
return accountInfoQueryService.findByAccountId(new EntityIdentifier(accountId))
.map(accountInfo -> new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance())));
@RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.GET)
public ResponseEntity<GetAccountResponse> get(@PathVariable String accountId) {
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
return ResponseEntity.ok().body(new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription()));
}
@RequestMapping(value = "/customers/{customerId}/accounts", method = RequestMethod.GET)
public ResponseEntity<GetAccountsResponse> getAccountsForCustomer(@PathVariable("customerId") String customerId) {
return ResponseEntity.ok().body(
new GetAccountsResponse(
accountInfoQueryService.findByCustomerId(customerId)
.stream()
.map(accountInfo -> new GetAccountResponse(
accountInfo.getId(),
new BigDecimal(accountInfo.getBalance()),
accountInfo.getTitle(),
accountInfo.getDescription()))
.collect(Collectors.toList())
)
);
}
@RequestMapping(value = "/accounts/{accountId}/history", method = RequestMethod.GET)
public ResponseEntity<AccountHistoryResponse> getTransactionsHistory(@PathVariable String accountId) {
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
List<AccountHistoryEntry> historyEntries = new ArrayList<>();
historyEntries.add(new AccountOpenInfo(accountInfo.getDate(), AccountHistoryEntry.EntryType.account, accountInfo.getChanges().get(0).getAmount()));
accountInfo.getTransactions().forEach(historyEntries::add);
return ResponseEntity.ok().body(new AccountHistoryResponse(historyEntries));
}
@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="account not found")
@ExceptionHandler(AccountNotFoundException.class)
public void accountNotFound() {
}
}

View File

@@ -0,0 +1,22 @@
apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: EventuateDependencyPlugin
dependencies {
compile project(":common-auth-web")
compile "org.apache.httpcomponents:httpclient:4.5"
compile "org.apache.httpcomponents:fluent-hc:4.5.1"
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
testCompile "junit:junit:4.11"
}
task copyWebStatic(type: Copy) {
from "../../js-frontend/build"
into "build/resources/main/static"
}
jar.dependsOn(copyWebStatic)

View File

@@ -0,0 +1,60 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* Created by popikyardo on 15.01.16.
*/
@ConfigurationProperties(prefix = "api.gateway")
public class ApiGatewayProperties {
private List<Endpoint> endpoints;
public static class Endpoint {
private String path;
private RequestMethod method;
private String location;
public Endpoint() {
}
public Endpoint(String location) {
this.location = location;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public RequestMethod getMethod() {
return method;
}
public void setMethod(RequestMethod method) {
this.method = method;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
public List<Endpoint> getEndpoints() {
return endpoints;
}
public void setEndpoints(List<Endpoint> endpoints) {
this.endpoints = endpoints;
}
}

View File

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

View File

@@ -0,0 +1,24 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.ResponseErrorHandler;
import java.io.IOException;
public class RestTemplateErrorHandler implements ResponseErrorHandler {
private static final Logger log = LoggerFactory.getLogger(RestTemplateErrorHandler.class);
@Override
public void handleError(ClientHttpResponse response) throws IOException {
log.error("Response error: {} {}", response.getStatusCode(), response.getStatusText());
}
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return RestUtil.isError(response.getStatusCode());
}
}

View File

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

View File

@@ -0,0 +1,91 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.controller;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayProperties;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.ContentRequestTransformer;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.HeadersRequestTransformer;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.URLRequestTransformer;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* Created by popikyardo on 15.01.16.
*/
@RestController
public class GatewayController {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ApiGatewayProperties apiGatewayProperties;
private HttpClient httpClient;
@PostConstruct
public void init() {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
}
@RequestMapping(value = "/api/**", method = {GET, POST})
@ResponseBody
public ResponseEntity<String> proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException {
HttpUriRequest proxiedRequest = createHttpUriRequest(request);
logger.info("request: {}", proxiedRequest);
HttpResponse proxiedResponse = httpClient.execute(proxiedRequest);
logger.info("Response {}", proxiedResponse.getStatusLine().getStatusCode());
return new ResponseEntity<>(read(proxiedResponse.getEntity().getContent()), processHeaders(proxiedResponse.getAllHeaders()), HttpStatus.valueOf(proxiedResponse.getStatusLine().getStatusCode()));
}
private HttpHeaders processHeaders(Header[] headers) {
HttpHeaders result = new HttpHeaders();
Stream.of(headers).filter(h -> h.getName().equalsIgnoreCase("Content-Type")).forEach( h -> result.set(h.getName(), h.getValue()));
return result;
}
private HttpUriRequest createHttpUriRequest(HttpServletRequest request) throws URISyntaxException, NoSuchRequestHandlingMethodException, IOException {
URLRequestTransformer urlRequestTransformer = new URLRequestTransformer(apiGatewayProperties);
ContentRequestTransformer contentRequestTransformer = new ContentRequestTransformer();
HeadersRequestTransformer headersRequestTransformer = new HeadersRequestTransformer();
headersRequestTransformer.setPredecessor(contentRequestTransformer);
contentRequestTransformer.setPredecessor(urlRequestTransformer);
return headersRequestTransformer.transform(request).build();
}
private String read(InputStream input) throws IOException {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) {
return buffer.lines().collect(Collectors.joining("\n"));
}
}
}

View File

@@ -0,0 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.main;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayServiceConfiguration;
import org.springframework.boot.SpringApplication;
/**
* Created by Main on 19.01.2016.
*/
public class ApiGatewayServiceMain {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayServiceConfiguration.class, args);
}
}

View File

@@ -0,0 +1,30 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.stream.Collectors;
/**
* Created by popikyardo on 21.01.16.
*/
public class ContentRequestTransformer extends ProxyRequestTransformer {
@Override
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
RequestBuilder requestBuilder = predecessor.transform(request);
String requestContent = request.getReader().lines().collect(Collectors.joining(""));
if (!requestContent.isEmpty()) {
StringEntity entity = new StringEntity(requestContent, ContentType.APPLICATION_JSON);
requestBuilder.setEntity(entity);
}
return requestBuilder;
}
}

View File

@@ -0,0 +1,31 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
import org.apache.http.client.methods.RequestBuilder;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Enumeration;
/**
* Created by popikyardo on 21.01.16.
*/
public class HeadersRequestTransformer extends ProxyRequestTransformer {
@Override
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException {
RequestBuilder requestBuilder = predecessor.transform(request);
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
if (headerName.equals("x-access-token")) {
requestBuilder.addHeader(headerName, headerValue);
}
}
return requestBuilder;
}
}

View File

@@ -0,0 +1,22 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
import org.apache.http.client.methods.RequestBuilder;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public abstract class ProxyRequestTransformer {
protected ProxyRequestTransformer predecessor;
public abstract RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException, IOException;
public void setPredecessor(ProxyRequestTransformer transformer) {
this.predecessor = transformer;
}
}

View File

@@ -0,0 +1,48 @@
package net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGatewayProperties;
import org.apache.http.client.methods.RequestBuilder;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public class URLRequestTransformer extends ProxyRequestTransformer {
private ApiGatewayProperties apiGatewayProperties;
public URLRequestTransformer(ApiGatewayProperties apiGatewayProperties) {
this.apiGatewayProperties = apiGatewayProperties;
}
@Override
public RequestBuilder transform(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, URISyntaxException {
String requestURI = request.getRequestURI();
URI uri;
if (request.getQueryString() != null && !request.getQueryString().isEmpty()) {
uri = new URI(getServiceUrl(requestURI, request) + "?" + request.getQueryString());
} else {
uri = new URI(getServiceUrl(requestURI, request));
}
RequestBuilder rb = RequestBuilder.create(request.getMethod());
rb.setUri(uri);
return rb;
}
private String getServiceUrl(String requestURI, HttpServletRequest httpServletRequest) throws NoSuchRequestHandlingMethodException {
ApiGatewayProperties.Endpoint endpoint =
apiGatewayProperties.getEndpoints().stream()
.filter(e ->
requestURI.matches(e.getPath()) && e.getMethod() == RequestMethod.valueOf(httpServletRequest.getMethod())
)
.findFirst().orElseThrow(() -> new NoSuchRequestHandlingMethodException(httpServletRequest));
return endpoint.getLocation() + requestURI;
}
}

View File

@@ -0,0 +1,25 @@
accounts.commandside.service.host=localhost
accounts.queryside.service.host=localhost
customers.commandside.service.host=localhost
customers.queryside.service.host=localhost
transfers.commandside.service.host=localhost
api.gateway.endpoints[0].path=[/]*api/accounts.*
api.gateway.endpoints[0].method=GET
api.gateway.endpoints[0].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[1].path=[/]*api/customers.*/accounts
api.gateway.endpoints[1].method=GET
api.gateway.endpoints[1].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[2].path=[/]*api/accounts.*
api.gateway.endpoints[2].method=POST
api.gateway.endpoints[2].location=http://${accounts.commandside.service.host}:8080
api.gateway.endpoints[3].path=[/]*api/customers.*
api.gateway.endpoints[3].method=GET
api.gateway.endpoints[3].location=http://${customers.queryside.service.host}:8080
api.gateway.endpoints[4].path=[/]*api/customers.*
api.gateway.endpoints[4].method=POST
api.gateway.endpoints[4].location=http://${customers.commandside.service.host}:8080
api.gateway.endpoints[5].path=[/]*api/transfers.*
api.gateway.endpoints[5].method=POST
api.gateway.endpoints[5].location=http://${transfers.commandside.service.host}:8080

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- [%thread] -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<root level="error">
<appender-ref ref="STDOUT" />
</root>
<logger name="org.springframework" level='info'>
</logger>
<logger name="net.chrisrichardson.eventstore.javaexamples.banking" level='info'>
</logger>
<logger name="io.eventuate" level='debug'>
</logger>
</configuration>

View File

@@ -5,9 +5,11 @@ dependencies {
testCompile project(":accounts-command-side-backend")
testCompile project(":transactions-command-side-backend")
testCompile project(":accounts-query-side-backend")
testCompile project(":customers-command-side-backend")
testCompile project(":customers-query-side-backend")
testCompile project(":testutil")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

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

View File

@@ -1,14 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend;
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
import net.chrisrichardson.eventstore.EntityWithMetadata;
import net.chrisrichardson.eventstore.EventStore;
import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventuateAggregateStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -16,18 +15,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import rx.Observable;
import java.math.BigDecimal;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=BankingTestConfiguration.class)
@SpringApplicationConfiguration(classes = BankingTestConfiguration.class)
@IntegrationTest
public class MoneyTransferIntegrationTest {
@@ -38,117 +34,57 @@ public class MoneyTransferIntegrationTest {
private MoneyTransferService moneyTransferService;
@Autowired
private EventStore eventStore;
private EventuateAggregateStore eventStore;
@Test
public void shouldTransferMoney() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
toAccount.getEntityIdentifier(),
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
toAccount.getEntityId(),
new BigDecimal(80))));
eventually (
new Producer<EntityWithMetadata<Account>>() {
@Override
public Observable<EntityWithMetadata<Account>> produce() {
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, fromAccount.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<Account>>() {
@Override
public void verify(EntityWithMetadata<Account> account) {
Assert.assertEquals(new BigDecimal(70), account.entity().getBalance());
}
});
eventually(
() -> eventStore.find(Account.class, fromAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(70), account.getEntity().getBalance()));
eventually (
new Producer<EntityWithMetadata<Account>>() {
@Override
public Observable<EntityWithMetadata<Account>> produce() {
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, toAccount.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<Account>>() {
@Override
public void verify(EntityWithMetadata<Account> account) {
Assert.assertEquals(new BigDecimal(380), account.entity().getBalance());
}
});
eventually(
() -> eventStore.find(Account.class, toAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(380), account.getEntity().getBalance()));
eventually (
new Producer<EntityWithMetadata<MoneyTransfer>>() {
@Override
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
return (Observable<EntityWithMetadata<MoneyTransfer>>)eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
@Override
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.entity().getState());
}
});
eventually(
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
}
@Test
public void shouldFailDueToInsufficientFunds() {
final EntityWithIdAndVersion<Account> fromAccount= await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
toAccount.getEntityIdentifier(),
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
toAccount.getEntityId(),
new BigDecimal(200))));
eventually (
new Producer<EntityWithMetadata<MoneyTransfer>>() {
@Override
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
return (Observable<EntityWithMetadata<MoneyTransfer>>)eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
@Override
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.entity().getState());
}
});
eventually(
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
updatedTransaction -> Assert.assertEquals(TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS, updatedTransaction.getEntity().getState()));
eventually (
new Producer<EntityWithMetadata<Account>>() {
@Override
public Observable<EntityWithMetadata<Account>> produce() {
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, fromAccount.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<Account>>() {
@Override
public void verify(EntityWithMetadata<Account> account) {
Assert.assertEquals(new BigDecimal(150), account.entity().getBalance());
}
});
eventually(
() -> eventStore.find(Account.class, fromAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(150), account.getEntity().getBalance()));
eventually (
new Producer<EntityWithMetadata<Account>>() {
@Override
public Observable<EntityWithMetadata<Account>> produce() {
return (Observable<EntityWithMetadata<Account>>)eventStore.find(Account.class, toAccount.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<Account>>() {
@Override
public void verify(EntityWithMetadata<Account> account) {
Assert.assertEquals(new BigDecimal(300), account.entity().getBalance());
}
});
eventually(
() -> eventStore.find(Account.class, toAccount.getEntityId()),
account -> Assert.assertEquals(new BigDecimal(300), account.getEntity().getBalance()));
}

View File

@@ -1,16 +1,13 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import net.chrisrichardson.eventstore.EntityWithIdAndVersion;
import net.chrisrichardson.eventstore.EntityWithMetadata;
import net.chrisrichardson.eventstore.EventStore;
import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventuateAggregateStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -18,9 +15,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import rx.Observable;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@@ -37,7 +34,7 @@ public class AccountQuerySideIntegrationTest {
private MoneyTransferService moneyTransferService;
@Autowired
private EventStore eventStore;
private EventuateAggregateStore eventStore;
@Autowired
private AccountQueryService accountQueryService;
@@ -45,54 +42,24 @@ public class AccountQuerySideIntegrationTest {
@Test
public void shouldUpdateQuerySide() throws Exception {
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount(new BigDecimal(150)));
final EntityWithIdAndVersion<Account> fromAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(150), ""));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount(new BigDecimal(300)));
final EntityWithIdAndVersion<Account> toAccount = await(accountService.openAccount("00000000-00000000", "My Account", new BigDecimal(300), ""));
final EntityWithIdAndVersion<MoneyTransfer> transaction = await(
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityIdentifier(),
toAccount.getEntityIdentifier(),
moneyTransferService.transferMoney(new TransferDetails(fromAccount.getEntityId(),
toAccount.getEntityId(),
new BigDecimal(80))));
eventually(
new Producer<EntityWithMetadata<MoneyTransfer>>() {
@Override
public Observable<EntityWithMetadata<MoneyTransfer>> produce() {
return eventStore.find(MoneyTransfer.class, transaction.getEntityIdentifier());
}
},
new Verifier<EntityWithMetadata<MoneyTransfer>>() {
@Override
public void verify(EntityWithMetadata<MoneyTransfer> updatedTransaction) {
Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.entity().getState());
}
});
() -> eventStore.find(MoneyTransfer.class, transaction.getEntityId()),
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
eventually(
new Producer<AccountInfo>() {
@Override
public Observable<AccountInfo> produce() {
return accountQueryService.findByAccountId(fromAccount.getEntityIdentifier());
}
},
new Verifier<AccountInfo>() {
@Override
public void verify(AccountInfo accountInfo) {
Assert.assertEquals(70*100, accountInfo.getBalance());
}
});
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(fromAccount.getEntityId())),
accountInfo -> Assert.assertEquals(70 * 100, accountInfo.getBalance()));
eventually(
new Producer<AccountInfo>() {
@Override
public Observable<AccountInfo> produce() {
return accountQueryService.findByAccountId(toAccount.getEntityIdentifier());
}
},
new Verifier<AccountInfo>() {
@Override
public void verify(AccountInfo accountInfo) {
Assert.assertEquals(380*100, accountInfo.getBalance());
}
});
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(toAccount.getEntityId())),
accountInfo -> Assert.assertEquals(380 * 100, accountInfo.getBalance()));
}
}

View File

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

View File

@@ -0,0 +1,74 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventuateAggregateStore;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Producer;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.Verifier;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.concurrent.CompletableFuture;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateCustomerInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateToAccountInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
/**
* Created by Main on 10.02.2016.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomerQuerySideTestConfiguration.class)
@IntegrationTest
public class CustomerQuerySideIntegrationTest {
@Autowired
private CustomerService customerService;
@Autowired
private CustomerQueryService customerQueryService;
@Autowired
private EventuateAggregateStore eventStore;
@Test
public void shouldCreateCustomerAndAddToAccount() throws Exception {
CustomerInfo customerInfo = generateCustomerInfo();
EntityWithIdAndVersion<Customer> customer = await(customerService.createCustomer(customerInfo));
ToAccountInfo toAccountInfo = generateToAccountInfo();
EntityWithIdAndVersion<Customer> customerWithNewAccount = await(customerService.addToAccount(customer.getEntityId(), toAccountInfo));
eventually(
new Producer<QuerySideCustomer>() {
@Override
public CompletableFuture<QuerySideCustomer> produce() {
return customerQueryService.findByCustomerId(customer.getEntityId());
}
},
new Verifier<QuerySideCustomer>() {
@Override
public void verify(QuerySideCustomer querySideCustomer) {
Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName());
Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
Assert.assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail());
Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
Assert.assertNotNull(querySideCustomer.getToAccounts());
Assert.assertFalse(querySideCustomer.getToAccounts().isEmpty());
Assert.assertEquals(querySideCustomer.getToAccounts().get("11111111-11111111"), toAccountInfo);
}
});
}
}

View File

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

View File

@@ -0,0 +1,16 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
class EventuateDependencyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.dependencies {
if (project.hasProperty("eventuateLocal")) {
compile "io.eventuate.local.java:eventuate-local-java-jdbc:${project.eventuateLocalVersion}"
compile "io.eventuate.local.java:eventuate-local-java-embedded-cdc-autoconfigure:${project.eventuateLocalVersion}"
} else
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:${project.eventuateClientVersion}"
}
}
}

View File

@@ -1,8 +1,11 @@
apply plugin: 'java'
dependencies {
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile project(":common-auth")
compile project(":common")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
testCompile "junit:junit:4.11"
}

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
/**
* Created by Main on 17.02.2016.
*/
public class ErrorResponse {
private String message;
public ErrorResponse() {
}
public ErrorResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,14 @@
apply plugin: 'java'
dependencies {
compile project(":common")
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
compile "org.springframework.security:spring-security-config:4.0.2.RELEASE"
compile "org.springframework.security:spring-security-web:4.0.2.RELEASE"
testCompile "junit:junit:4.11"
}

View File

@@ -0,0 +1,97 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.token.KeyBasedPersistenceTokenService;
import org.springframework.security.core.token.TokenService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import java.security.SecureRandom;
/**
* Created by popikyardo on 21.09.15.
*/
@Configuration
@ComponentScan
@EnableWebSecurity
@EnableMongoRepositories
@EnableConfigurationProperties({AuthProperties.class})
public class AuthConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private AuthProperties securityProperties;
@Autowired
private TokenAuthenticationService tokenAuthenticationService;
@Autowired
CustomerAuthService customerAuthService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication();
auth.userDetailsService(userDetailsServiceBean());
}
@Override
public UserDetailsService userDetailsServiceBean() {
return email -> {
/* QuerySideCustomer customer = customerAuthService.findByEmail(email);
if (customer != null) {
return new User(email);
} else {
throw new UsernameNotFoundException(String.format("could not find the customer '%s'", email));
}*/
//authorize everyone with basic authentication
return new User(email, "", true, true, true, true,
AuthorityUtils.createAuthorityList("USER"));
};
}
@Bean
public CustomerAuthService customerAuthService(CustomerAuthRepository customerAuthRepository) {
return new CustomerAuthService(customerAuthRepository);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().and()
.authorizeRequests()
.antMatchers("/index.html", "/", "/**.js", "/**.css").permitAll()
.antMatchers("/swagger-ui.html", "/v2/api-docs").permitAll()
.antMatchers(HttpMethod.POST, "/api/customers", "/api/login").permitAll()
.anyRequest().authenticated().and()
.addFilterAfter(new StatelessAuthenticationFilter(tokenAuthenticationService), BasicAuthenticationFilter.class);
}
@Bean
public TokenService tokenService() {
KeyBasedPersistenceTokenService res = new KeyBasedPersistenceTokenService();
res.setSecureRandom(new SecureRandom());
res.setServerSecret(securityProperties.getServerSecret());
res.setServerInteger(securityProperties.getServerInteger());
return res;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.User;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.UserAuthentication;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.token.Token;
import org.springframework.security.core.token.TokenService;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
@Service
public class TokenAuthenticationService {
@Autowired
private TokenService tokenService;
private static final String AUTH_HEADER_NAME = "access-token";
private static final long DAY = 1000 * 60 * 60 * 24;
private ObjectMapper mapper = new ObjectMapper();
public Authentication getAuthentication(HttpServletRequest request) throws IOException {
final String tokenString = request.getHeader(AUTH_HEADER_NAME);
if (tokenString != null) {
Token token = tokenService.verifyToken(tokenString);
final User user = mapper.readValue(token.getExtendedInformation(), User.class);
if (user != null && (System.currentTimeMillis() - token.getKeyCreationTime()) < DAY) {
return new UserAuthentication(user);
}
}
return null;
}
}

View File

@@ -0,0 +1,33 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.TokenAuthenticationService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService;
public StatelessAuthenticationFilter(TokenAuthenticationService taService) {
this.tokenAuthenticationService = taService;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(
tokenAuthenticationService.getAuthentication((HttpServletRequest) req));
}
chain.doFilter(req, res);
}
}

View File

@@ -0,0 +1,78 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by popikyardo on 23.09.15.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails {
private String email;
public User() {
}
public User(String email) {
this.email = email;
}
public void setUsername(String username) {
this.email = username;
}
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("USER");
Set<GrantedAuthority> res = new HashSet<GrantedAuthority>();
res.add(authority);
return res;
}
@Override
public String getPassword() {
return "";
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return false;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}

View File

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

View File

@@ -0,0 +1,2 @@
auth.serverSecret=the_cake_is_a_lie
auth.serverInteger=1

View File

@@ -1,11 +1,13 @@
apply plugin: 'java'
dependencies {
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile project(":common")
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "net.chrisrichardson.eventstore.client:eventstore-jdbc_2.10:$eventStoreClientVersion"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -1,16 +1,14 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
import java.math.BigDecimal;
public class AccountChangedEvent implements Event {
protected BigDecimal amount;
protected EntityIdentifier transactionId;
protected String transactionId;
public AccountChangedEvent(BigDecimal amount, EntityIdentifier transactionId) {
public AccountChangedEvent(BigDecimal amount, String transactionId) {
this.amount = amount;
this.transactionId = transactionId;
}
@@ -18,7 +16,7 @@ public class AccountChangedEvent implements Event {
public AccountChangedEvent() {
}
public EntityIdentifier getTransactionId() {
public String getTransactionId() {
return transactionId;
}

View File

@@ -1,8 +1,5 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import java.math.BigDecimal;
public class AccountCreditedEvent extends AccountChangedEvent {
@@ -10,7 +7,7 @@ public class AccountCreditedEvent extends AccountChangedEvent {
private AccountCreditedEvent() {
}
public AccountCreditedEvent(BigDecimal amount, EntityIdentifier transactionId) {
public AccountCreditedEvent(BigDecimal amount, String transactionId) {
super(amount, transactionId);
}

View File

@@ -1,20 +1,18 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
public class AccountDebitFailedDueToInsufficientFundsEvent implements Event {
private EntityIdentifier transactionId;
private String transactionId;
private AccountDebitFailedDueToInsufficientFundsEvent() {
}
public AccountDebitFailedDueToInsufficientFundsEvent(EntityIdentifier transactionId) {
public AccountDebitFailedDueToInsufficientFundsEvent(String transactionId) {
this.transactionId = transactionId;
}
public EntityIdentifier getTransactionId() {
public String getTransactionId() {
return transactionId;
}
}

View File

@@ -1,8 +1,5 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import net.chrisrichardson.eventstore.Aggregate;
import net.chrisrichardson.eventstore.EntityIdentifier;
import java.math.BigDecimal;
public class AccountDebitedEvent extends AccountChangedEvent {
@@ -10,7 +7,7 @@ public class AccountDebitedEvent extends AccountChangedEvent {
private AccountDebitedEvent() {
}
public AccountDebitedEvent(BigDecimal amount, EntityIdentifier transactionId) {
public AccountDebitedEvent(BigDecimal amount, String transactionId) {
super(amount, transactionId);
}

View File

@@ -1,22 +1,40 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
import java.math.BigDecimal;
public class AccountOpenedEvent implements Event {
private String customerId;
private String title;
private BigDecimal initialBalance;
private String description;
private AccountOpenedEvent() {
}
public AccountOpenedEvent(BigDecimal initialBalance) {
public AccountOpenedEvent(String customerId, String title, BigDecimal initialBalance, String description) {
this.customerId = customerId;
this.title = title;
this.initialBalance = initialBalance;
this.description = description;
}
public String getCustomerId() {
return customerId;
}
public String getTitle() {
return title;
}
public BigDecimal getInitialBalance() {
return initialBalance;
}
public String getDescription() {
return description;
}
}

View File

@@ -1,2 +1,2 @@
@net.chrisrichardson.eventstore.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account")
@io.eventuate.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.Account")
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
/**
* Created by Main on 08.02.2016.
*/
public class CustomerAddedToAccount extends CustomerEvent {
private ToAccountInfo toAccountInfo;
public CustomerAddedToAccount() {
}
public CustomerAddedToAccount(ToAccountInfo toAccountInfo) {
this.toAccountInfo = toAccountInfo;
}
public ToAccountInfo getToAccountInfo() {
return toAccountInfo;
}
public void setToAccountInfo(ToAccountInfo toAccountInfo) {
this.toAccountInfo = toAccountInfo;
}
}

View File

@@ -0,0 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
/**
* Created by popikyardo on 02.02.16.
*/
public class CustomerCreatedEvent extends CustomerEvent {
private CustomerInfo customerInfo;
public CustomerCreatedEvent() {
}
public CustomerCreatedEvent(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
public void setCustomerInfo(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
}

View File

@@ -0,0 +1,12 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
import io.eventuate.Event;
import io.eventuate.EventEntity;
/**
* Created by Main on 11.02.2016.
*/
@EventEntity(entity = "net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.Customer")
public abstract class CustomerEvent implements Event {
}

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
public class CreditRecordedEvent implements Event {
private TransferDetails details;

View File

@@ -1,6 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
import net.chrisrichardson.eventstore.Event;
// import io.eventuate.Event;
import io.eventuate.Event;
public class DebitRecordedEvent implements Event {
private TransferDetails details;

View File

@@ -1,6 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
public class FailedDebitRecordedEvent implements Event {
private TransferDetails details;

View File

@@ -1,7 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;
import net.chrisrichardson.eventstore.Event;
import io.eventuate.Event;
public class MoneyTransferCreatedEvent implements Event {
private TransferDetails details;

View File

@@ -4,34 +4,50 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.trans
case class TransferDetails(fromAccountId : EntityIdentifier, toAccountId : EntityIdentifier, amount : BigDecimal)
*/
import net.chrisrichardson.eventstore.EntityIdentifier;
import java.math.BigDecimal;
import java.util.Date;
public class TransferDetails {
private EntityIdentifier fromAccountId;
private EntityIdentifier toAccountId;
private String fromAccountId;
private String toAccountId;
private BigDecimal amount;
private Date date;
private String description;
private TransferDetails() {
}
public TransferDetails(EntityIdentifier fromAccountId, EntityIdentifier toAccountId, BigDecimal amount) {
public TransferDetails(String fromAccountId, String toAccountId, BigDecimal amount) {
this(fromAccountId, toAccountId, amount, new Date(), "");
}
public TransferDetails(String fromAccountId, String toAccountId, BigDecimal amount, Date date, String description) {
this.fromAccountId = fromAccountId;
this.toAccountId = toAccountId;
this.amount = amount;
this.date = date;
this.description = description;
}
public EntityIdentifier getFromAccountId() {
public String getFromAccountId() {
return fromAccountId;
}
public EntityIdentifier getToAccountId() {
public String getToAccountId() {
return toAccountId;
}
public BigDecimal getAmount() {
return amount;
}
public Date getDate() {
return date;
}
public String getDescription() {
return description;
}
}

View File

@@ -1,2 +1,2 @@
@net.chrisrichardson.eventstore.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer")
@io.eventuate.EventEntity(entity="net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer")
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions;

View File

@@ -16,7 +16,7 @@
<logger name="org.springframework" level='info'>
</logger>
<logger name="net.chrisrichardson.eventstore.client" level='info'>
<logger name="io.eventuate" level='debug'>
</logger>
</configuration>

View File

@@ -1,23 +1,22 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import io.eventuate.javaclient.commonimpl.JSonMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import net.chrisrichardson.eventstore.json.EventStoreCommonObjectMapping;
import net.chrisrichardson.utils.json.JSonMapper;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
public class AccountOpenEventSerializationTest {
public class AccountOpenEventSerializationTest {
@Test
public void shouldSerde() {
AccountOpenedEvent event = new AccountOpenedEvent(new BigDecimal(55));
String json = JSonMapper.toJson(event, EventStoreCommonObjectMapping.getObjectMapper());
AccountOpenedEvent event = new AccountOpenedEvent("00000000-00000000", "My Account", new BigDecimal(55), "");
String json = JSonMapper.toJson(event);
System.out.println("json=" + json);
AccountOpenedEvent event2 = JSonMapper.fromJSon(AccountOpenedEvent.class, json, EventStoreCommonObjectMapping.getObjectMapper());
AccountOpenedEvent event2 = JSonMapper.fromJson(json, AccountOpenedEvent.class);
Assert.assertEquals(event.getInitialBalance(), event2.getInitialBalance());
}

View File

@@ -1,5 +1,5 @@
dependencies {
compile "net.chrisrichardson.eventstore.client:eventstore-java-client_2.10:$eventStoreClientVersion"
compile "io.eventuate.client.java:eventuate-client-java-spring:$eventuateClientVersion"
compile "io.springfox:springfox-swagger2:2.2.2"
compile 'io.springfox:springfox-swagger-ui:2.2.2'

View File

@@ -6,14 +6,14 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import rx.Observable;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.concurrent.CompletableFuture;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
@Configuration
@@ -27,7 +27,7 @@ public class CommonSwaggerConfiguration {
.apis(RequestHandlerSelectors.basePackage("net.chrisrichardson.eventstore.javaexamples.banking"))
.build()
.pathMapping("/")
.genericModelSubstitutes(ResponseEntity.class, Observable.class)
.genericModelSubstitutes(ResponseEntity.class, CompletableFuture.class)
.alternateTypeRules(
newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),

View File

@@ -1,33 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.util;
import org.springframework.web.context.request.async.DeferredResult;
import rx.Observable;
import rx.Subscriber;
import java.util.concurrent.atomic.AtomicReference;
public class DeferredUtils {
public static <T> DeferredResult<T> toDeferredResult(Observable<T> o) {
final DeferredResult<T> d = new DeferredResult<T>();
final AtomicReference<T> r = new AtomicReference<T>();
o.single().subscribe(new Subscriber<T>() {
@Override
public void onCompleted() {
d.setResult(r.get());
}
@Override
public void onError(Throwable e) {
d.setErrorResult(e);
}
@Override
public void onNext(T t) {
r.set(t);
}
});
return d;
}
}

View File

@@ -1,31 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.util;
import rx.Observable;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
public class ObservableReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return Observable.class.equals(returnType.getParameterType());
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
return;
}
DeferredResult<?> d = DeferredUtils.toDeferredResult((Observable<?>) returnValue);
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(d, mavContainer);
}
}

View File

@@ -0,0 +1,8 @@
apply plugin: 'java'
dependencies {
compile "commons-lang:commons-lang:2.6"
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
testCompile group: 'junit', name: 'junit', version: '4.11'
}

View File

@@ -0,0 +1,80 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import java.util.Date;
public class AccountChangeInfo {
private String changeId;
private String transactionId;
private String transactionType;
private long amount;
private long balanceDelta;
public AccountChangeInfo() {
}
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this(new Date(), changeId, transactionId, transactionType, amount, balanceDelta);
}
public AccountChangeInfo(Date date, String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this.changeId = changeId;
this.transactionId = transactionId;
this.transactionType = transactionType;
this.amount = amount;
this.balanceDelta = balanceDelta;
}
public String getChangeId() {
return changeId;
}
public void setChangeId(String changeId) {
this.changeId = changeId;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getTransactionType() {
return transactionType;
}
public void setTransactionType(String transactionType) {
this.transactionType = transactionType;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
public long getBalanceDelta() {
return balanceDelta;
}
public void setBalanceDelta(long balanceDelta) {
this.balanceDelta = balanceDelta;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -0,0 +1,50 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.Date;
/**
* Created by popikyardo on 9/1/16.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "entryType")
@JsonSubTypes({
@JsonSubTypes.Type(value = AccountTransactionInfo.class, name = "transaction"),
@JsonSubTypes.Type(value = AccountOpenInfo.class, name = "account")
})
public class AccountHistoryEntry {
protected Date date;
protected EntryType entryType;
public AccountHistoryEntry() {
}
public AccountHistoryEntry(Date date, EntryType entryType) {
this.date = date;
this.entryType = entryType;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public EntryType getEntryType() {
return entryType;
}
public void setEntryType(EntryType entryType) {
this.entryType = entryType;
}
public enum EntryType {
transaction, account
}
}

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