├── .classpath
├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── libraries
│ ├── SBT__com_google_code_findbugs_jsr305_1_3_9_jar.xml
│ ├── SBT__com_google_guava_guava_12_0_jar.xml
│ ├── SBT__com_google_protobuf_protobuf_java_2_5_0_jar.xml
│ ├── SBT__com_typesafe_akka_akka_actor_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_akka_akka_cluster_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_akka_akka_contrib_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_akka_akka_persistence_experimental_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_akka_akka_remote_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_akka_akka_testkit_2_10_2_3_8_jar.xml
│ ├── SBT__com_typesafe_config_1_2_1_jar.xml
│ ├── SBT__com_typesafe_slick_slick_2_10_2_1_0_jar.xml
│ ├── SBT__io_netty_netty_3_8_0_Final_jar.xml
│ ├── SBT__mysql_mysql_connector_java_5_1_34_jar.xml
│ ├── SBT__org_fusesource_hawtjni_hawtjni_runtime_1_8_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_1_7_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_all_1_7_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_linux32_1_5_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_linux64_1_5_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_osx_1_5_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_win32_1_5_jar.xml
│ ├── SBT__org_fusesource_leveldbjni_leveldbjni_win64_1_5_jar.xml
│ ├── SBT__org_iq80_leveldb_leveldb_0_5_jar.xml
│ ├── SBT__org_iq80_leveldb_leveldb_api_0_5_jar.xml
│ ├── SBT__org_scala_lang_scala_library_2_10_4_jar.xml
│ ├── SBT__org_slf4j_slf4j_api_1_6_4_jar.xml
│ └── SBT__org_uncommons_maths_uncommons_maths_1_2_2a_jar.xml
├── misc.xml
├── modules.xml
├── modules
│ ├── akkadynodb-build.iml
│ └── akkadynodb.iml
├── sbt.xml
├── scala_compiler.xml
├── scopes
│ └── scope_settings.xml
├── uiDesigner.xml
├── vcs.xml
└── workspace.xml
├── .project
├── README.md
├── build.sbt
├── images
├── cluster.png
├── node.png
├── node_detail.png
├── ring.png
└── todo.png
├── project
└── plugins.sbt
└── src
└── main
├── resources
├── application.conf
├── application.conf~
└── rss.conf
└── scala
├── client
└── RSSClient.scala
├── constants
└── Constants.scala
├── database
├── Db.scala
├── columns
│ └── Columns.scala
├── exceptions
│ ├── ActiveSlickException.scala
│ ├── ManyRowsAffectedException.scala
│ ├── NoRowsAffectedException.scala
│ ├── RowNotFoundException.scala
│ └── StaleObjectStateException.scala
├── tableQueries
│ └── TableQueries.scala
└── tables
│ └── Tables.scala
├── demo
└── Demo.scala
├── models
├── Identifiable.scala
├── Versionable.scala
└── VersionedData.scala
├── replication
└── Replicator.scala
├── schema
├── Entities.scala
├── Entity.scala
└── EntityUtils.scala
├── service
└── ReactiveStorageService.scala
└── storage
└── StorageNode.scala
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | .cache/
6 | .history/
7 | .lib/
8 | dist/*
9 | target/
10 | lib_managed/
11 | src_managed/
12 | project/boot/
13 | project/plugins/project/
14 |
15 | # Scala-IDE specific
16 | .scala_dependencies
17 | .worksheet
18 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | AkkaDynoDB
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_google_code_findbugs_jsr305_1_3_9_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_google_guava_guava_12_0_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_google_protobuf_protobuf_java_2_5_0_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_actor_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_cluster_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_contrib_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_persistence_experimental_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_remote_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_akka_akka_testkit_2_10_2_3_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_config_1_2_1_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__com_typesafe_slick_slick_2_10_2_1_0_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__io_netty_netty_3_8_0_Final_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__mysql_mysql_connector_java_5_1_34_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_hawtjni_hawtjni_runtime_1_8_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_1_7_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_all_1_7_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_linux32_1_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_linux64_1_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_osx_1_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_win32_1_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_fusesource_leveldbjni_leveldbjni_win64_1_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_iq80_leveldb_leveldb_0_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_iq80_leveldb_leveldb_api_0_5_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_scala_lang_scala_library_2_10_4_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_slf4j_slf4j_api_1_6_4_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/libraries/SBT__org_uncommons_maths_uncommons_maths_1_2_2a_jar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules/akkadynodb-build.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/.idea/modules/akkadynodb.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.idea/sbt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/scala_compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/uiDesigner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
8 | -
9 |
10 |
11 | -
12 |
13 |
14 | -
15 |
16 |
17 | -
18 |
19 |
20 |
21 |
22 |
23 | -
24 |
25 |
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
33 |
34 |
35 | -
36 |
37 |
38 |
39 |
40 |
41 | -
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 |
50 |
51 | -
52 |
53 |
54 |
55 |
56 | -
57 |
58 |
59 |
60 |
61 | -
62 |
63 |
64 |
65 |
66 | -
67 |
68 |
69 |
70 |
71 | -
72 |
73 |
74 | -
75 |
76 |
77 |
78 |
79 | -
80 |
81 |
82 |
83 |
84 | -
85 |
86 |
87 |
88 |
89 | -
90 |
91 |
92 |
93 |
94 | -
95 |
96 |
97 |
98 |
99 | -
100 |
101 |
102 | -
103 |
104 |
105 | -
106 |
107 |
108 | -
109 |
110 |
111 | -
112 |
113 |
114 |
115 |
116 | -
117 |
118 |
119 | -
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | Android Lint
186 |
187 |
188 | General
189 |
190 |
191 | Maven
192 |
193 |
194 | Plugin DevKit
195 |
196 |
197 | XPath
198 |
199 |
200 |
201 |
202 | Android
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
776 |
777 |
778 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 | 1422556688627
813 |
814 | 1422556688627
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
957 |
958 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
966 |
967 |
968 |
969 |
970 |
971 |
972 |
973 |
974 |
975 |
976 |
977 |
978 |
979 |
980 |
981 |
982 |
983 |
984 |
985 |
986 |
987 |
988 |
989 |
990 |
991 |
992 |
993 |
994 |
995 |
996 |
997 |
998 |
999 |
1000 |
1001 |
1002 |
1003 |
1004 |
1005 |
1006 |
1007 |
1008 |
1009 |
1010 |
1011 |
1012 |
1013 |
1014 |
1015 |
1016 |
1017 |
1018 |
1019 |
1020 |
1021 |
1022 |
1023 |
1024 |
1025 |
1026 |
1027 |
1028 |
1029 |
1030 |
1031 |
1032 |
1033 |
1034 |
1035 |
1036 |
1037 |
1038 |
1039 |
1040 |
1041 |
1042 |
1043 |
1044 |
1045 |
1046 |
1047 |
1048 |
1049 |
1050 |
1051 |
1052 |
1053 |
1054 |
1055 |
1056 |
1057 |
1058 |
1059 |
1060 |
1061 |
1062 |
1063 |
1064 |
1065 |
1066 |
1067 |
1068 |
1069 |
1070 |
1071 |
1072 |
1073 |
1074 |
1075 |
1076 |
1077 |
1078 |
1079 |
1080 |
1081 |
1082 |
1083 |
1084 |
1085 |
1086 |
1087 |
1088 |
1089 |
1090 |
1091 |
1092 |
1093 |
1094 |
1095 |
1096 |
1097 |
1098 |
1099 |
1100 |
1101 |
1102 |
1103 |
1104 |
1105 |
1106 |
1107 |
1108 |
1109 |
1110 |
1111 |
1112 |
1113 |
1114 |
1115 |
1116 |
1117 |
1118 |
1119 |
1120 |
1121 |
1122 |
1123 |
1124 |
1125 |
1126 |
1127 |
1128 |
1129 |
1130 |
1131 |
1132 |
1133 |
1134 |
1135 |
1136 |
1137 |
1138 |
1139 |
1140 |
1141 |
1142 |
1143 |
1144 |
1145 |
1146 |
1147 |
1148 |
1149 |
1150 |
1151 |
1152 |
1153 |
1154 |
1155 |
1156 |
1157 |
1158 |
1159 |
1160 |
1161 |
1162 |
1163 |
1164 |
1165 |
1166 |
1167 |
1168 |
1169 |
1170 |
1171 |
1172 |
1173 |
1174 |
1175 |
1176 |
1177 |
1178 |
1179 |
1180 |
1181 |
1182 |
1183 |
1184 |
1185 |
1186 |
1187 |
1188 |
1189 |
1190 |
1191 |
1192 |
1193 |
1194 |
1195 |
1196 |
1197 |
1198 |
1199 |
1200 |
1201 |
1202 |
1203 |
1204 |
1205 |
1206 |
1207 |
1208 |
1209 |
1210 |
1211 |
1212 |
1213 |
1214 |
1215 |
1216 |
1217 |
1218 |
1219 |
1220 |
1221 |
1222 |
1223 |
1224 |
1225 |
1226 |
1227 |
1228 |
1229 |
1230 |
1231 |
1232 |
1233 |
1234 |
1235 |
1236 |
1237 |
1238 |
1239 |
1240 |
1241 |
1242 |
1243 |
1244 |
1245 |
1246 |
1247 |
1248 |
1249 |
1250 |
1251 |
1252 |
1253 |
1254 |
1255 |
1256 |
1257 |
1258 |
1259 |
1260 |
1261 |
1262 |
1263 |
1264 |
1265 |
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 |
1273 |
1274 |
1275 |
1276 |
1277 |
1278 |
1279 |
1280 |
1281 |
1282 |
1283 |
1284 |
1285 |
1286 |
1287 |
1288 |
1289 |
1290 |
1291 |
1292 |
1293 |
1294 |
1295 |
1296 | No facets are configured
1297 |
1298 |
1299 |
1300 |
1301 |
1302 |
1303 |
1304 |
1305 |
1306 |
1307 |
1308 | scala-sdk-2.10.4
1309 |
1310 |
1311 |
1312 |
1313 |
1314 |
1315 |
1316 |
1317 |
1318 |
1319 |
1320 | 1.7
1321 |
1322 |
1323 |
1324 |
1325 |
1326 |
1327 |
1328 |
1329 |
1330 |
1331 |
1332 | akkadynodb
1333 |
1334 |
1335 |
1336 |
1337 |
1338 |
1339 |
1340 |
1341 |
1342 |
1343 |
1344 |
1345 | 1.7
1346 |
1347 |
1348 |
1349 |
1350 |
1351 |
1352 |
1353 |
1354 |
1355 |
1356 |
1357 | SBT: com.google.code.findbugs:jsr305:1.3.9:jar
1358 |
1359 |
1360 |
1361 |
1362 |
1363 |
1364 |
1365 |
1366 |
1367 |
1368 |
1369 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 | AkkaDynoDB
3 |
4 |
5 | org.scala-ide.sdt.core.scalabuilder
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalanature
10 | org.eclipse.jdt.core.javanature
11 |
12 |
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AkkaDynoDB (Reactive Storage Service)
2 | ==========================================================
3 |
4 | Dynamo like distributed database built using Akka Cluster
5 |
6 | ## **Introduction**
7 |
8 | _**AkkaDynoDB**_ is a _reactive_ _storage_ _service_ inspired from the Amazon dynamo distributed database
9 | which is Highly available, scalable and resilient database [All Things distributed Paper](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf).
10 | With changing requirements of enterprise applications the way we build applications has been changing. With increasing number of
11 | devices that connect to the internet the traffic per server has drastically changed over years and its going to increase much more in the
12 | near future with the emergence of Internet of things(IOT). In order to cater to the changing requirements which cannot be handled by traditional architectures
13 | many enterprises started adopting distributed architectures to spread load over large number of machines and handle user requests. These applications should also
14 | handle data effectively and offer reliability to its users. As the number of users increasing the applications must also scalable well
15 | according to the need. Not only scalability and reliability users also expect 100% availability that means 0 down times even in case of
16 | database maintenance and data migration. In order to offer these essential features enterprises have been adopting certain best practices and techniques from
17 | different researches. _**Reactive design principles**_ basically convey these best practices and how systems should be built from ground up to be scalable,
18 | available and be resilient.
19 |
20 | ## **Reactive Systems**
21 |
22 | _**Reactive Systems**_ has these four traits
23 |
24 | 1. Responsive (Available in a responding state)
25 |
26 | 2. Resilient (Fault tolerant)
27 |
28 | 3. Elastic (Scalable)
29 |
30 | 4. Message Driven (Communicate by sending messages i.e core to distribution)
31 |
32 | Please read [Reactive Manifesto](http://www.reactivemanifesto.org/) to know about what is to be reactive.
33 |
34 | ## **Akka**
35 |
36 | _**Akka**_ as mentioned here [akka.io](http://akka.io) is a toolkit and runtime for building
37 |
38 | 1. Highly Concurrent
39 |
40 | 2. Distributed
41 |
42 | 3. Resilient
43 |
44 | 4. Message-Driven applications on JVM (Java Virtual Machine)
45 |
46 | Akka adheres to the reactive principles and offers abstractions to deal with concurrency, distribution and fault tolerance.
47 |
48 | 1. For concurrency Akka offers Scala Futures and Agents. (Helps Scale up)
49 |
50 | 2. For Distribution and Remoting it offers Actors. (Helps Scale Out)
51 |
52 | 3. For Fault tolerance Actor Supervision. (Deal with Failure)
53 |
54 | Note: The above mentioned is not exhaustive list of what akka offers. Please visit [Akka Website](http://akka.io) for more.
55 |
56 | More about actors [Actor Model](http://arxiv.org/pdf/1008.1459.pdf), [Actor Model](http://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-194.pdf).
57 |
58 | ## **Actor**
59 |
60 | _**Actor Model**_ of concurrent computation provides a primitive called as **Actor**.
61 |
62 | Actor is an entity which can do these three things
63 |
64 | 1. Communicate with message passing (can communicate)
65 |
66 | 2. Create new actors (Can create new actors)
67 |
68 | 3. decide what to do with next message (Can change its behaviour on receiving a message)
69 |
70 | Actors can also have mutable state and can take local decisions. Actors can be used for distributing work among different machines.
71 | With the help of actors work can be distributed among other worker actors and performed in a concurrent and distributed manner.
72 | Actors provide location transparency by which same semantics of communication that are used for local actors can be used with remote actors.
73 | Running potential long running code inside the actor makes the actor deaf to the messages that are sent to it. So, it is recommend to
74 | wrap long running code inside a Future and execute it.Akka provides handy syntax to do the same.
75 |
76 |
77 | ```scala
78 |
79 | object MasterActor {
80 | case object StartWork
81 | }
82 |
83 | class MasterActor extends Actor with ActorLogging {
84 | def receive = {
85 | case StartWork => {
86 | val future = Future {
87 | longRunningCode
88 | }
89 | future pipeTo self //pipe feature
90 | }
91 | case _ => log.info("unknown message")
92 | }
93 | def longRunningCode: Unit = Thread.sleep(1000000)
94 | }
95 |
96 | ```
97 |
98 | ## **Akka Cluster**
99 |
100 | Akka Cluster
101 |
102 |
103 | Akka Cluster provides a fault tolerant decentralized peer-to-peer based cluster membership service with no
104 | single point of failure or single point of bottleneck. It does this using gossip protocols and a automatic failure
105 | detector [Akka Cluster Specification Intro](http://doc.akka.io/docs/akka/snapshot/common/cluster.html#cluster)
106 |
107 |
108 | Please have a look at Akka Cluster documentation here [Akka Cluster](http://akka.io/docs)
109 |
110 |
111 | ## **AkkaDynoDB Architecture**
112 |
113 | To do
114 |
115 | 
116 |
117 | Overall AkkaDynoDB looks like this
118 |
119 | 
120 |
121 | Each Node in the cluster sends heart beats to every other node in the cluster to know whether a particluar
122 | node is dead or alive.AkkaDynoDb uses Akka Cluster so, all that applies to Akka Cluster also applies to
123 | AkkaDynoDB
124 |
125 |
126 | Interactions of single Node of Cluster
127 |
128 | 
129 |
130 | The above picture depicts the interactions of the single node with other nodes. All nodes are in consistent hashing ring
131 |
132 | Node in detail
133 |
134 | 
135 |
136 | Each node in the cluster consists of these three components
137 |
138 | 1. Service Actor
139 | 2. ConsistentHashing Router in the Service Actor
140 | 3. Storage Actor which persists the data into store
141 |
142 | ## **Service Actor**
143 |
144 | _**Service Actor**_ receives all the client requests to persist and retrieve the data. Then the request is
145 | sent to the Consistent Hashing router. The request consists of the Key which helps the Router to dispatch
146 | the request to the final node in which the data is stored. If the key belongs to the Node itself
147 | then the request is sent to the Storage Actor itself and will not be routed.
148 |
149 |
150 | ## **Consistent Hashing Router**
151 |
152 | This is inside the _**Service Actor**_ . The responsibility of the router is to take the request and dispatch
153 | it to the corresponding node which is responsible for handling the data. This is done by using the key in the request.
154 | Also Consistent Hashing Router helps in both read and write request scalability there by preventing hot stops in the system.
155 |
156 |
157 |
158 | ## **Storage Actor**
159 |
160 | _**Storage Actor**_ is responsible for persisting the data and retrieving the data that is requested by the
161 | Service Actor and this request is dispatched by the consistent hashing router in the service node.
162 |
163 | ## **Router**
164 |
165 | Routers in Akka helps in balancing/distribution the load on the system by dispatching the request to different worker
166 | actors based on some criteria.There are two kinds of routers
167 |
168 | 1. Pool Router (creates workers by itself)
169 | 2. Group Router (Give created workers to the router, Group router does not create any worker actors by itself)
170 |
171 | ## **Consistent Hashing**
172 |
173 | 
174 |
175 | Consistent Hashing Router routes messages to workers based on Consistent Hashing Algorithm
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | name := """AkkaDynoDB"""
2 |
3 | version := "1.0.0"
4 |
5 | sbtVersion := "0.13.8"
6 |
7 | mainClass := Some("""database.Db""")
8 |
9 | libraryDependencies ++= Seq("com.typesafe.slick" %% "slick" % "2.1.0",
10 | "mysql" % "mysql-connector-java" % "5.1.34",
11 | "com.typesafe.akka" %% "akka-actor" % "2.3.8",
12 | "com.typesafe.akka" %% "akka-cluster" % "2.3.8",
13 | "com.typesafe.akka" %% "akka-remote" % "2.3.8",
14 | "com.typesafe.akka" %% "akka-contrib" % "2.3.8",
15 | "com.typesafe.akka" %% "akka-testkit" % "2.3.8")
--------------------------------------------------------------------------------
/images/cluster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pamu/AkkaDynoDB/0c25bf850a5fb401e9d10cff096c0c5b5d8142a6/images/cluster.png
--------------------------------------------------------------------------------
/images/node.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pamu/AkkaDynoDB/0c25bf850a5fb401e9d10cff096c0c5b5d8142a6/images/node.png
--------------------------------------------------------------------------------
/images/node_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pamu/AkkaDynoDB/0c25bf850a5fb401e9d10cff096c0c5b5d8142a6/images/node_detail.png
--------------------------------------------------------------------------------
/images/ring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pamu/AkkaDynoDB/0c25bf850a5fb401e9d10cff096c0c5b5d8142a6/images/ring.png
--------------------------------------------------------------------------------
/images/todo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pamu/AkkaDynoDB/0c25bf850a5fb401e9d10cff096c0c5b5d8142a6/images/todo.png
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.5.0")
2 |
3 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
--------------------------------------------------------------------------------
/src/main/resources/application.conf:
--------------------------------------------------------------------------------
1 | akka {
2 | actor {
3 | provider = "akka.cluster.ClusterActorRefProvider"
4 | }
5 | remote {
6 | log-remote-lifecycle-events = off
7 | netty.tcp {
8 | hostname = "127.0.0.1"
9 | port = 0
10 | }
11 | }
12 |
13 | cluster {
14 | seed-nodes = [
15 | "akka.tcp://ClusterSystem@127.0.0.1:2551",
16 | "akka.tcp://ClusterSystem@127.0.0.1:2552",
17 | "akka.tcp://ClusterSystem@127.0.0.1:2553"]
18 |
19 | auto-down-unreachable-after = 10s
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/resources/application.conf~:
--------------------------------------------------------------------------------
1 | akka {
2 | actor {
3 | provider = "akka.cluster.ClusterActorRefProvider"
4 | }
5 | remote {
6 | log-remote-lifecycle-events = off
7 | netty.tcp {
8 | hostname = "10.8.6.191"
9 | port = 0
10 | }
11 | }
12 |
13 | cluster {
14 | seed-nodes = [
15 | "akka.tcp://ClusterSystem@10.8.15.59:2552",
16 | "akka.tcp://ClusterSystem@10.8.6.191:2551",
17 | "akka.tcp://ClusterSystem@10.8.6.191:2553"]
18 |
19 | auto-down-unreachable-after = 10s
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/resources/rss.conf:
--------------------------------------------------------------------------------
1 | include "application"
2 |
3 | akka.actor.deployment {
4 | /ReactiveStorageService/Router {
5 | router = consistent-hashing-group
6 | nr-of-instances = 100
7 | routees.paths = ["/user/StorageNode"]
8 | virtual-nodes-factor = 100
9 | cluster {
10 | enabled = on
11 | allow-local-routees = on
12 | use-role = storage
13 | }
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/main/scala/client/RSSClient.scala:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import akka.actor._
4 | import akka.cluster.{MemberStatus, Cluster}
5 | import akka.cluster.ClusterEvent._
6 | import constants.Constants
7 | import storage.StorageNode.{Entry, Message}
8 |
9 | //import database.tableQueries.TableWithIdQuery
10 | //import database.tables.IdTable
11 |
12 | //import scala.concurrent.duration._
13 | //import scala.concurrent.forkjoin.ThreadLocalRandom
14 |
15 | /**
16 | * Created by android on 10/3/15.
17 | */
18 |
19 | object RSSClient {
20 | trait Result
21 | final case class Error(msg: String) extends Result
22 | final case class Success(value: Any) extends Result
23 | }
24 |
25 | class RSSClient(servicePath: String) extends Actor with ActorLogging {
26 |
27 | import RSSClient._
28 |
29 | val cluster = Cluster(context.system)
30 | val servicePathElements = servicePath match {
31 | case RelativeActorPath(elements) => elements
32 | case _ => throw new IllegalArgumentException(
33 | "servicePath [%s] is not a valid relative error path" format servicePath
34 | )
35 | }
36 |
37 | override def preStart(): Unit = {
38 | cluster.subscribe(self, classOf[MemberEvent], classOf[ReachabilityEvent])
39 | }
40 |
41 | override def postStop(): Unit = {
42 | cluster.unsubscribe(self)
43 | //tickTask.cancel()
44 | }
45 |
46 | //import context.dispatcher
47 |
48 | //val tickTask = context.system.scheduler.schedule(2.seconds, 10.seconds, self, "tick")
49 |
50 | var nodes = Set.empty[Address]
51 |
52 | var seq = 0
53 |
54 | /*
55 | import scala.slick.driver.MySQLDriver.simple._
56 |
57 | case class User(name: String, id: Option[Long] = None)
58 |
59 | class Users(tag: Tag) extends IdTable[User, Long](tag, "users") {
60 | def name = column[String]("name")
61 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
62 | def * = (name, id.?) <> (User.tupled, User.unapply)
63 | }
64 |
65 | val users = new TableWithIdQuery[User, Long, Users](tag => new Users(tag)) {
66 | /**
67 | * Extracts the model Id of a arbitrary model.
68 | * @param model a mapped model
69 | * @return a Some[I] if Id is filled, None otherwise
70 | */
71 | override def extractId(model: User): Option[Long] = model.id
72 |
73 | /**
74 | *
75 | * @param model a mapped model (usually without an assigned id).
76 | * @param id an id, usually generate by the database
77 | * @return a model M with an assigned id.
78 | */
79 | override def withId(model: User, id: Long): User = model.copy(id = Some(id))
80 | }**/
81 |
82 | override def receive = {
83 |
84 | case "tick" if !nodes.isEmpty => {
85 |
86 | /**
87 | log.info("sending request")
88 | //pick the random Akka Storage Service Instance
89 | val address = nodes.toIndexedSeq(ThreadLocalRandom.current.nextInt(nodes.size))
90 | val service = context.actorSelection(RootActorPath(address) / servicePathElements)
91 | //val random = ThreadLocalRandom.current().nextInt(1, 10)
92 | seq += 1
93 | log.info(nodes.mkString)
94 | log.info(s"Generated seq number $seq and hitting node {}", service)
95 | val random = seq
96 | if(1 == 0) {
97 |
98 | log.info("{}", storage.StorageNode.Entry(s"key$random", s"value$random").toString)
99 |
100 | service ! storage.StorageNode.Entry(s"key$random", s"value$random")
101 |
102 | } else {
103 |
104 | log.info("{}", storage.StorageNode.Entry(s"key$random", s"value$random").toString)
105 |
106 | service ! storage.StorageNode.Get(s"name$random")
107 | }
108 | **/
109 | log.info(s"${nodes.toIndexedSeq.size} nodes registered")
110 | self ! Entry(seq.toString, "Pamu Nagarjuna")
111 | seq += 1
112 | log.info("{} sent.", Entry(1.toString, "Pamu Nagarjuna"))
113 |
114 | //self ! storage.StorageNode.Entry[User, Long, Users](users, 1L, User("pamu nagarjuna"))
115 | }
116 |
117 | case message: Message =>
118 | val address = nodes.toIndexedSeq(0)
119 | val service = context.actorSelection(RootActorPath(address) / servicePathElements)
120 | service ! message
121 |
122 | case result: Result =>
123 | result match {
124 | case Error(msg) => log.info(msg)
125 | case Success(value) =>
126 | value match {
127 | case msg: String => log.info(msg)
128 | case value => log.info("{}", value)
129 | }
130 | }
131 |
132 | case state: CurrentClusterState => nodes = state.members.collect {
133 | case member if member.hasRole("storage") && member.status == MemberStatus.Up => member.address
134 | }
135 |
136 | case MemberUp(member) if member.hasRole("storage") => nodes += member.address
137 | case other: MemberEvent => nodes -= other.member.address
138 | case UnreachableMember(member) => nodes -= member.address
139 | case ReachableMember(member) => nodes += member.address
140 |
141 | case junk => log.info("unknown message of type: {} => {}", junk getClass, junk)
142 | }
143 | }
144 |
145 | // Wrapper object for the Cluster bootstrap code
146 | object Starter {
147 | def main(args: Array[String]): Unit = {
148 | val system = ActorSystem("ClusterSystem")
149 | val client = system.actorOf(Props(classOf[RSSClient], "/user/ReactiveStorageService"), Constants.client)
150 | case class User(name: String, id: Long)
151 | val pamu = User("pamu", 1L)
152 | Thread.sleep(5000)
153 | client ! Entry(pamu.id.toString, pamu)
154 | }
155 | }
156 |
157 | object Client {
158 | val system = ActorSystem("ClusterSystem")
159 | val client = system.actorOf(Props(classOf[RSSClient], "/user/ReactiveStorageService"), Constants.client)
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/scala/constants/Constants.scala:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | /**
4 | * Created by pamu on 13/4/15.
5 | */
6 | object Constants {
7 | val ReactiveStorageService = "ReactiveStorageService"
8 | val Router = "Router"
9 | val StorageNode = "StorageNode"
10 | val client = "Client"
11 | val Replicator = "Replicator"
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/scala/database/Db.scala:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | /**
4 | * Created by android on 8/3/15.
5 | */
6 |
7 | import akka.actor.{Props, ActorSystem, Actor}
8 | import database.tableQueries.TableWithIdQuery
9 | import database.tables.IdTable
10 |
11 | import scala.slick.driver.MySQLDriver.simple._
12 |
13 | object Db {
14 |
15 | lazy val db = Database.forURL(
16 | url = "jdbc:mysql://localhost/demo",
17 | driver = "com.mysql.jdbc.Driver",
18 | user="root",
19 | password="root")
20 |
21 | case class User(name: String, id: Option[Long] = None)
22 |
23 | class Users(tag: Tag) extends IdTable[User, Long](tag, "users") {
24 | def name = column[String]("name")
25 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
26 | def * = (name, id.?) <> (User.tupled, User.unapply)
27 | }
28 |
29 | val users = new TableWithIdQuery[User, Long, Users](tag => new Users(tag)) {
30 | /**
31 | * Extracts the model Id of a arbitrary model.
32 | * @param model a mapped model
33 | * @return a Some[I] if Id is filled, None otherwise
34 | */
35 | override def extractId(model: User): Option[Long] = model.id
36 |
37 | /**
38 | *
39 | * @param model a mapped model (usually without an assigned id).
40 | * @param id an id, usually generate by the database
41 | * @return a model M with an assigned id.
42 | */
43 | override def withId(model: User, id: Long): User = model.copy(id = Some(id))
44 | }
45 |
46 |
47 |
48 | def main(args: Array[String]): Unit = {
49 | val sys = ActorSystem("system")
50 | val dbActor = sys actorOf(Props[DbActor], "DbActor")
51 | import DbActor._
52 | for(i <- 1 to 100)
53 | dbActor ! Entry[User, Long, Users](users, User("pamu nagarjuna"))
54 | }
55 | }
56 |
57 | object DbActor {
58 | case class Entry[M, I: BaseColumnType, T <: IdTable[M, I]](tableWithIdQuery: TableWithIdQuery[M, I, T], model: M)
59 | }
60 |
61 | class DbActor extends Actor {
62 | import DbActor._
63 | override def receive = {
64 | case Entry(tableWithIdQuery, model) => Db.db.withSession { implicit sx =>
65 | tableWithIdQuery.createIfNotExists
66 | tableWithIdQuery.save(model)
67 | }
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/src/main/scala/database/columns/Columns.scala:
--------------------------------------------------------------------------------
1 | package database.columns
2 |
3 | import scala.slick.driver.MySQLDriver.simple._
4 | /**
5 | * Created by xmax on 7/4/15.
6 | */
7 |
8 | trait IdColumn[I] {
9 | def id: Column[I]
10 | }
11 |
12 | trait VersionColumn {
13 | def version: Column[Long]
14 | }
--------------------------------------------------------------------------------
/src/main/scala/database/exceptions/ActiveSlickException.scala:
--------------------------------------------------------------------------------
1 | package database.exceptions
2 |
3 | class ActiveSlickException(msg: String) extends RuntimeException(msg)
4 |
--------------------------------------------------------------------------------
/src/main/scala/database/exceptions/ManyRowsAffectedException.scala:
--------------------------------------------------------------------------------
1 | package database.exceptions
2 |
3 | case class ManyRowsAffectedException(affectedRecordsCount: Int)
4 | extends ActiveSlickException(s"Expected single row affected, got $affectedRecordsCount instead")
--------------------------------------------------------------------------------
/src/main/scala/database/exceptions/NoRowsAffectedException.scala:
--------------------------------------------------------------------------------
1 | package database.exceptions
2 |
3 | case object NoRowsAffectedException extends ActiveSlickException("No rows affected")
4 |
--------------------------------------------------------------------------------
/src/main/scala/database/exceptions/RowNotFoundException.scala:
--------------------------------------------------------------------------------
1 | package database.exceptions
2 |
3 | case class RowNotFoundException[T](notFoundRecord: T)
4 | extends ActiveSlickException(s"Row not found: $notFoundRecord")
--------------------------------------------------------------------------------
/src/main/scala/database/exceptions/StaleObjectStateException.scala:
--------------------------------------------------------------------------------
1 | package database.exceptions
2 |
3 | import models.Versionable
4 |
5 | case class StaleObjectStateException[T <: Versionable[T]](staleObject: T, current: T)
6 | extends ActiveSlickException(s"Optimistic locking error - object in stale state: $staleObject, current in DB: $current")
--------------------------------------------------------------------------------
/src/main/scala/database/tableQueries/TableQueries.scala:
--------------------------------------------------------------------------------
1 | package database.tableQueries
2 |
3 | import database.tables.IdTable
4 | import database.tables.Tables.EntityTable
5 | import models.{Identifiable, Versionable}
6 | import database.tables.Tables._
7 |
8 | import scala.slick.driver.MySQLDriver.simple._
9 |
10 | import database.exceptions.{StaleObjectStateException, ManyRowsAffectedException, NoRowsAffectedException, RowNotFoundException}
11 |
12 | import scala.slick.jdbc.meta.MTable
13 | import scala.slick.lifted.TableQuery
14 | import scala.util.{Failure, Success, Try}
15 |
16 | /**
17 | * Created by xmax on 7/4/15.
18 | */
19 |
20 | abstract class ActiveTableQuery[M, T <: Table[M]](cons: Tag => T) extends TableQuery(cons) {
21 |
22 | def createIfNotExists(implicit sess: Session) = if(MTable.getTables(baseTableRow.tableName).list.isEmpty) this.ddl.create
23 |
24 | def count(implicit sess: Session): Int = length.run
25 |
26 | def fetchAll(implicit sess: Session): List[M] = this.list
27 |
28 | def pagedList(pageIndex: Int, limit: Int)(implicit sess: Session): List[M] =
29 | drop(pageIndex).take(limit).run.toList
30 |
31 | def save(model: M)(implicit sess: Session): M = trySave(model).get
32 |
33 | def update(model: M)(implicit sess: Session): M = tryUpdate(model).get
34 |
35 | def delete(model: M)(implicit sess: Session): Unit = tryDelete(model).get
36 |
37 | /**
38 | * Try to save the model.
39 | * @return A Success[M] is case of success, Failure otherwise.
40 | */
41 | def trySave(model: M)(implicit sess: Session): Try[M]
42 |
43 | /**
44 | * Try to delete the model.
45 | * @return A Success[Unit] is case of success, Failure otherwise.
46 | */
47 | def tryDelete(model: M)(implicit sess: Session): Try[Unit]
48 |
49 | /**
50 | * Try to update the model.
51 | * @return A Success[M] is case of success, Failure otherwise.
52 | */
53 | def tryUpdate(model: M)(implicit sess: Session): Try[M]
54 |
55 | }
56 |
57 | abstract class TableWithIdQuery[M, I: BaseColumnType, T <: IdTable[M, I]](cons: Tag => T)
58 | extends ActiveTableQuery[M, T](cons) {
59 |
60 | /**
61 | * Extracts the model Id of a arbitrary model.
62 | * @param model a mapped model
63 | * @return a Some[I] if Id is filled, None otherwise
64 | */
65 | def extractId(model: M): Option[I]
66 |
67 | def tryExtractId(model: M)(implicit sess: Session): Try[I] = {
68 | extractId(model) match {
69 | case Some(id) => Success(id)
70 | case None => Failure(RowNotFoundException(model))
71 | }
72 | }
73 |
74 | /**
75 | *
76 | * @param model a mapped model (usually without an assigned id).
77 | * @param id an id, usually generate by the database
78 | * @return a model M with an assigned id.
79 | */
80 | def withId(model: M, id: I): M
81 |
82 | def filterById(id: I)(implicit sess: Session) = filter(_.id === id)
83 |
84 | /**
85 | * Define an insert query that returns the database generated identifier.
86 | * @param model a mapped model
87 | * @return the database generated identifier.
88 | */
89 | def add(model: M)(implicit sess: Session): I = tryAdd(model).get
90 |
91 | def tryAdd(model: M)(implicit sess: Session): Try[I] = {
92 | rollbackOnFailure {
93 | Try(this.returning(this.map(_.id)).insert(model))
94 | }
95 | }
96 |
97 | protected def rollbackOnFailure[R](query: => Try[R])(implicit sess: Session): Try[R] = {
98 | val tried = query
99 |
100 | if (tried.isFailure && !sess.conn.getAutoCommit)
101 | sess.rollback()
102 |
103 | tried
104 | }
105 |
106 | protected def mustAffectOneSingleRow(query: => Int): Try[Unit] = {
107 |
108 | val affectedRows = query
109 |
110 | if (affectedRows == 1) Success(Unit)
111 | else if (affectedRows == 0) Failure(NoRowsAffectedException)
112 | else Failure(ManyRowsAffectedException(affectedRows))
113 |
114 | }
115 |
116 | override def tryUpdate(model: M)(implicit sess: Session): Try[M] = {
117 | rollbackOnFailure {
118 | tryExtractId(model).flatMap { id =>
119 | tryUpdate(id, model)
120 | }
121 | }
122 | }
123 |
124 | override def trySave(model: M)(implicit sess: Session): Try[M] = {
125 | rollbackOnFailure {
126 | extractId(model) match {
127 | // if has an Id, try to update it
128 | case Some(id) => tryUpdate(id, model)
129 |
130 | // if has no Id, try to add it
131 | case None => tryAdd(model).map { id => withId(model, id) }
132 | }
133 | }
134 | }
135 |
136 | protected def tryUpdate(id: I, model: M)(implicit sess: Session): Try[M] = {
137 | mustAffectOneSingleRow {
138 | filterById(id).update(model)
139 | }.recoverWith {
140 | // if nothing gets updated, we want a Failure[RowNotFoundException]
141 | // all other failures must be propagated
142 | case NoRowsAffectedException => Failure(RowNotFoundException(model))
143 |
144 | }.map { _ =>
145 | model // return a Try[M] if only one row is affected
146 | }
147 | }
148 |
149 | override def tryDelete(model: M)(implicit sess: Session): Try[Unit] = {
150 | rollbackOnFailure {
151 | tryExtractId(model).flatMap { id =>
152 | tryDeleteById(id)
153 | }
154 | }
155 | }
156 |
157 | def deleteById(id: I)(implicit sess: Session): Unit = tryDeleteById(id).get
158 |
159 | def tryDeleteById(id: I)(implicit sess: Session): Try[Unit] = {
160 | rollbackOnFailure {
161 | mustAffectOneSingleRow {
162 | filterById(id).delete
163 |
164 | }.recoverWith {
165 | // if nothing gets deleted, we want a Failure[RowNotFoundException]
166 | // all other failures must be propagated
167 | case NoRowsAffectedException => Failure(RowNotFoundException(id))
168 | }
169 | }
170 | }
171 |
172 | def tryFindById(id: I)(implicit sess: Session): Try[M] = {
173 | findOptionById(id) match {
174 | case Some(model) => Success(model)
175 | case None => Failure(RowNotFoundException(id))
176 | }
177 | }
178 |
179 | def findById(id: I)(implicit sess: Session): M = findOptionById(id).get
180 |
181 | def findOptionById(id: I)(implicit sess: Session): Option[M] = filterById(id).firstOption
182 | }
183 |
184 | class EntityTableQuery[M <: Identifiable[M], T <: EntityTable[M]](cons: Tag => T)(implicit ev1: BaseColumnType[M#Id])
185 | extends TableWithIdQuery[M, M#Id, T](cons) {
186 |
187 | def extractId(identifiable: M) = identifiable.id
188 |
189 | def withId(entity: M, id: M#Id) = entity.withId(id)
190 | }
191 |
192 | class VersionableEntityTableQuery[M <: Versionable[M] with Identifiable[M], T <: VersionableEntityTable[M]](cons: Tag => T)(implicit ev1: BaseColumnType[M#Id])
193 | extends EntityTableQuery[M, T](cons) {
194 |
195 | override protected def tryUpdate(id: M#Id, versionable: M)(implicit sess: Session): Try[M] = {
196 |
197 | val queryById = filter(_.id === id)
198 | val queryByIdAndVersion = queryById.filter(_.version === versionable.version)
199 | val modelWithNewVersion = versionable.withVersion(versionable.version + 1)
200 |
201 | mustAffectOneSingleRow {
202 | queryByIdAndVersion.update(modelWithNewVersion)
203 |
204 | }.recoverWith {
205 | // no updates?
206 | case NoRowsAffectedException =>
207 | // if row exists we have a stale object
208 | // all other failures must be propagated
209 | tryFindById(id).flatMap { currentOnDb =>
210 | Failure(StaleObjectStateException(versionable, currentOnDb))
211 | }
212 |
213 | }.map { _ =>
214 | modelWithNewVersion // return the versionable entity with an updated version
215 | }
216 | }
217 |
218 | override def trySave(versionable: M)(implicit sess: Session): Try[M] = {
219 | rollbackOnFailure {
220 | extractId(versionable) match {
221 | // if has an Id, try to update it
222 | case Some(id) => tryUpdate(id, versionable)
223 |
224 | // if has no Id, try to add it
225 | case None =>
226 | // init versioning
227 | val modelWithVersion = versionable.withVersion(1)
228 | tryAdd(modelWithVersion).map { id => withId(modelWithVersion, id) }
229 | }
230 | }
231 | }
232 |
233 | }
234 |
235 | object EntityTableQuery {
236 | def apply[M <: Identifiable[M], T <: EntityTable[M]](cons: Tag => T)(implicit ev1: BaseColumnType[M#Id]) =
237 | new EntityTableQuery[M, T](cons)
238 | }
239 |
240 | object VersionableEntityTableQuery {
241 | def apply[M <: Versionable[M] with Identifiable[M], T <: VersionableEntityTable[M]](cons: Tag => T)(implicit ev1: BaseColumnType[M#Id]) =
242 | new VersionableEntityTableQuery[M, T](cons)
243 | }
--------------------------------------------------------------------------------
/src/main/scala/database/tables/Tables.scala:
--------------------------------------------------------------------------------
1 | package database.tables
2 |
3 | import database.columns.{VersionColumn, IdColumn}
4 | import models.{Versionable, Identifiable}
5 |
6 | import scala.slick.driver.MySQLDriver.simple._
7 |
8 | /**
9 | * Created by xmax on 7/4/15.
10 | */
11 |
12 | abstract class IdTable[M, I](tag: Tag, schemaName: Option[String], tableName: String)(implicit val colType: BaseColumnType[I])
13 | extends Table[M](tag, schemaName, tableName) with IdColumn[I] {
14 | def this(tag: Tag, tableName: String)(implicit mapping: BaseColumnType[I]) = this(tag, None, tableName)
15 | }
16 |
17 | abstract class IdVersionTable[M, I](tag: Tag, schemaName: Option[String], tableName: String)(override implicit val colType: BaseColumnType[I])
18 | extends IdTable[M, I](tag, schemaName, tableName)(colType) with VersionColumn {
19 | def this(tag: Tag, tableName: String)(implicit mapping: BaseColumnType[I]) = this(tag, None, tableName)
20 | }
21 |
22 | object Tables {
23 | type EntityTable[M <: Identifiable[M]] = IdTable[M, M#Id]
24 | type VersionableEntityTable[M <: Identifiable[M] with Versionable[M]] = IdVersionTable[M, M#Id]
25 | }
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/scala/demo/Demo.scala:
--------------------------------------------------------------------------------
1 | package demo
2 |
3 | import akka.actor.{ActorLogging, Actor}
4 |
5 | /**
6 | import akka.actor.Actor
7 |
8 | import scala.slick.driver.MySQLDriver.simple._
9 |
10 | /**
11 | * Created by pamu on 1/4/15.
12 | */
13 |
14 | object Demo {
15 | final case class Entry[M](tableQuery: TableQuery[Table[M]], model: M)
16 | final case class Get[M](tableQuery: TableQuery[Table[M]], id: Long)
17 | final case class Evict[M](tableQuery: TableQuery[Table[M]], id: Long)
18 | }
19 |
20 | class Demo extends Actor {
21 | import Demo._
22 | import database.Db._
23 | def receive = {
24 | case Entry(tableQuery, model) => {
25 | db.withSession {
26 | implicit sx => tableQuery += model
27 | }
28 | }
29 | case Get(tableQuery, id) => {
30 | db.withSession {
31 | implicit sx => {
32 | val query = for(model <- tableQuery.filter(_.id === id)) yield model
33 | query.firstOption
34 | }
35 | }
36 | }
37 | case Evict(tableQuery, id) => {
38 | db.withSession {
39 | implicit sx => {
40 | val query = for(model <- tableQuery.filter(_.id === id)) yield model
41 | query.delete
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
48 | object Starter {
49 | def main(args: Array[String]): Unit = {
50 | println("Starter")
51 | }
52 | }
53 | object Greeter {
54 | case object Hi
55 | case object Hello
56 | }
57 |
58 | class Greeter extends Actor with ActorLogging {
59 | import Greeter._
60 | override def receive = {
61 | case Hi => sender() ! Hello
62 | case _ => log.info("unknown message")
63 | }
64 | }**/
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/main/scala/models/Identifiable.scala:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | /**
4 | * Base trait to define a model having an ID (i.e.: Entity).
5 | * The ID is defined as a type alias as it needs to
6 | * be accessed by ActiveSlick via type projection when mapping to database tables.
7 | */
8 | trait Identifiable[E <: Identifiable[E]] {
9 |
10 | /** The type of this Entity ID */
11 | type Id
12 |
13 | /**
14 | * The Entity ID wrapped in an Option.
15 | * Expected to be None when Entity not yet persisted, otherwise Some[Id]
16 | */
17 | def id: Option[E#Id]
18 |
19 | /**
20 | * Provide the means to assign an ID to the entity
21 | * @return A copy of this Entity with an ID.
22 | */
23 | def withId(id: E#Id): E
24 | }
--------------------------------------------------------------------------------
/src/main/scala/models/Versionable.scala:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | trait Versionable[E <: Versionable[E]] {
4 | def version: Long
5 | def withVersion(id: Long): E
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/src/main/scala/models/VersionedData.scala:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import akka.cluster.VectorClock
4 |
5 | /**
6 | * Created by xmax on 30/3/15.
7 | */
8 | case class VersionedData[T](id: Long, version: VectorClock, data: T)
9 |
--------------------------------------------------------------------------------
/src/main/scala/replication/Replicator.scala:
--------------------------------------------------------------------------------
1 | package replication
2 |
3 | import akka.actor.{Actor, ActorLogging}
4 | import akka.cluster.ClusterEvent.{CurrentClusterState, MemberUp}
5 | import akka.cluster.{Cluster, MemberStatus}
6 | import storage.StorageNode
7 |
8 | /**
9 | * Created by pnagarjuna on 27/05/15.
10 | */
11 |
12 | class Replicator extends Actor with ActorLogging {
13 | var cache = scala.collection.immutable.ListMap.empty[String, Any]
14 |
15 | val cluster = Cluster(context.system)
16 |
17 | // on actor pre start
18 | override def preStart(): Unit = cluster.subscribe(self, classOf[MemberUp])
19 |
20 | // on actor post stop
21 | override def postStop(): Unit = cluster.unsubscribe(self)
22 |
23 | import StorageNode._
24 |
25 | override def receive = {
26 |
27 | case state: CurrentClusterState => state.members.filter(_.status == MemberStatus.Up).
28 | foreach(x => log.info(s"${x.address} is Up"))
29 |
30 | case MemberUp(member) => log.info("member {} is up", member.address)
31 |
32 | case Entry(key, value) =>
33 | cache += (key -> value)
34 | log.info("{}", cache.mkString("\n", "\n", "\n"))
35 |
36 | case Get(key) =>
37 | log.info("{}", Get(key))
38 |
39 | case Evict(key) =>
40 | log.info("{}", Evict(key))
41 | if (cache contains key) {
42 | cache -= key
43 | }
44 | case x => log.info("unknown message {} of type {}", x, x getClass)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/schema/Entities.scala:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | /**
4 | * Created by pnagarjuna on 21/05/15.
5 | */
6 |
7 | import database.tables.IdTable
8 |
9 | import scala.slick.driver.MySQLDriver.simple._
10 |
11 | class Entities(tag: Tag) extends IdTable[Entity, Long](tag, "Entities") {
12 | def key = column[String]("key", O.NotNull)
13 | def value = column[String]("value", O.NotNull)
14 | def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
15 | def * = (key, value, id.?) <> (Entity.tupled, Entity.unapply)
16 | }
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/scala/schema/Entity.scala:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | /**
4 | * Created by pnagarjuna on 21/05/15.
5 | */
6 | case class Entity(key: String, value: String, id: Option[Long] = None)
7 |
--------------------------------------------------------------------------------
/src/main/scala/schema/EntityUtils.scala:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import database.tableQueries.TableWithIdQuery
4 |
5 | import scala.util.Try
6 |
7 | /**
8 | * Created by pnagarjuna on 21/05/15.
9 | */
10 | object EntityUtils {
11 | import scala.slick.driver.MySQLDriver.simple._
12 |
13 | lazy val entities = new TableWithIdQuery[Entity, Long, Entities](tag => new Entities(tag)) {
14 | /**
15 | * Extracts the model Id of a arbitrary model.
16 | * @param model a mapped model
17 | * @return a Some[I] if Id is filled, None otherwise
18 | */
19 | override def extractId(model: Entity): Option[Long] = model.id
20 |
21 | /**
22 | *
23 | * @param model a mapped model (usually without an assigned id).
24 | * @param id an id, usually generate by the database
25 | * @return a model M with an assigned id.
26 | */
27 | override def withId(model: Entity, id: Long): Entity = model.copy(id = Some(id))
28 | }
29 |
30 | def getEntity(id: Long)(implicit db: Database): Try[Entity] = db.withSession {implicit sx => {
31 | entities.tryFindById(id)
32 | }}
33 |
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/scala/service/ReactiveStorageService.scala:
--------------------------------------------------------------------------------
1 | package service
2 |
3 | import akka.actor.{Props, ActorSystem, Actor}
4 | import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope
5 | import akka.routing.FromConfig
6 | import com.typesafe.config.ConfigFactory
7 | import constants.Constants
8 | import replication.Replicator
9 | import storage.StorageNode
10 |
11 | /**
12 | * Created by android on 10/3/15.
13 | */
14 |
15 |
16 | class ReactiveStorageService extends Actor {
17 |
18 | val workerRouter = context.actorOf(FromConfig.props(Props[StorageNode]), name = Constants.Router)
19 | // import the worker node message get, entry, evict
20 | import StorageNode._
21 |
22 | // actor receive method
23 | override def receive = {
24 |
25 | case Get(key) => {
26 | //Get message from the client
27 | //Wrap the message in the consistent hashable envelope and send it to the router
28 | workerRouter forward ConsistentHashableEnvelope(message = Get(key), hashKey = key)
29 | }
30 | case Entry(key, value) => {
31 | //Entry message from the client to add the key to the store
32 | //wrap the message in the envelope
33 | workerRouter forward ConsistentHashableEnvelope(message = Entry(key,value), hashKey = key)
34 | }
35 |
36 | case Evict(key) => {
37 | //Evict operation message from the client
38 | //wrap the evict key in the envelope
39 | workerRouter forward ConsistentHashableEnvelope(message = Evict(key), hashKey = key)
40 | }
41 |
42 | case All(key) => {
43 | //All operation message from the client
44 | //wrap the all key in the envelope
45 | workerRouter forward ConsistentHashableEnvelope(message = All(key), hashKey = key)
46 | }
47 | }
48 | }
49 |
50 |
51 | object Starter {
52 |
53 | def main(args: Array[String]): Unit = {
54 | //use the port number given as command line args or use random port
55 | val port = if(args.isEmpty) "0" else args(0)
56 |
57 | //read the configuration in the file rss.conf
58 | val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port")
59 | .withFallback(ConfigFactory.parseString("akka.cluster.roles = [storage]"))
60 | .withFallback(ConfigFactory.load("rss"))
61 |
62 | //get the ref of the actor system
63 | val system = ActorSystem("ClusterSystem", config)
64 |
65 | //start the worker actor which does the real storing stuff
66 | system.actorOf(Props[StorageNode], name = Constants.StorageNode)
67 |
68 | //starting akka storage service actor
69 | system.actorOf(Props[ReactiveStorageService], name = Constants.ReactiveStorageService)
70 |
71 | //starting akka replicator service actor
72 | //system.actorOf(Props[Replicator], Constants.Replicator)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/scala/storage/StorageNode.scala:
--------------------------------------------------------------------------------
1 | package storage
2 |
3 | import akka.actor._
4 | import akka.cluster.{UniqueAddress, MemberStatus, Cluster}
5 | import akka.cluster.ClusterEvent._
6 | import com.typesafe.config.ConfigFactory
7 | import constants.Constants
8 | import replication.Replicator
9 |
10 | //import database.tableQueries.TableWithIdQuery
11 | //import database.tables.IdTable
12 | //import models.Identifiable
13 |
14 | import scala.concurrent.Future
15 | //import scala.slick.driver.MySQLDriver.simple._
16 |
17 | import akka.pattern.pipe
18 |
19 | import scala.util.{Failure, Success}
20 |
21 | /**
22 | * Created by android on 8/3/15.
23 | */
24 |
25 | object StorageNode {
26 | /**
27 | trait DbMessage
28 | final case class Entry[M, I: BaseColumnType, T <: IdTable[M, I]](tableWithIdQuery: TableWithIdQuery[M, I, T], key: I, model: M) extends DbMessage
29 | final case class Evict[M, I: BaseColumnType, T <: IdTable[M, I]](tableWithIdQuery: TableWithIdQuery[M, I, T], key: I) extends DbMessage
30 | final case class Get[M, I: BaseColumnType, T <: IdTable[M, I]](tableWithIdQuery: TableWithIdQuery[M, I, T], key: I) extends DbMessage**/
31 | trait Message extends Serializable {
32 | val key: String
33 | }
34 | final case class Entry(override val key: String, value: Any) extends Message
35 | final case class Evict(override val key: String) extends Message
36 | final case class Get(override val key: String) extends Message
37 | final case class All(override val key: String) extends Message
38 | final case class ReplicaEntry(override val key: String, value: Any) extends Message
39 | }
40 |
41 |
42 | class StorageNode extends Actor with ActorLogging {
43 |
44 | var cache = scala.collection.immutable.ListMap.empty[String, Any]
45 | var nodes = Set.empty[UniqueAddress]
46 |
47 | /**
48 | lazy val db = Database.forURL(
49 | url = s"jdbc:mysql://localhost/demo${cluster.selfAddress.hostPort}",
50 | driver = "com.mysql.jdbc.Driver",
51 | user="root",
52 | password="root")**/
53 |
54 | //val replicator = context.system.actorOf(Props[Replicator], Constants.Replicator)
55 |
56 | val cluster = Cluster(context.system)
57 |
58 | // on actor pre start
59 | override def preStart(): Unit = cluster.subscribe(self, classOf[MemberUp])
60 |
61 | // on actor post stop
62 | override def postStop(): Unit = cluster.unsubscribe(self)
63 |
64 | import StorageNode._
65 | import client.RSSClient
66 |
67 | //import context.dispatcher
68 |
69 | override def receive = {
70 |
71 | case state: CurrentClusterState => state.members.filter(_.status == MemberStatus.Up).
72 | foreach(x => log.info(s"${x.address} is Up"))
73 |
74 | case MemberUp(member) if member.hasRole("storage") => nodes += member.uniqueAddress
75 | case other: MemberEvent => nodes -= other.member.uniqueAddress
76 | case UnreachableMember(member) => nodes -= member.uniqueAddress
77 | case ReachableMember(member) => nodes += member.uniqueAddress
78 |
79 | case Entry(key, value) =>
80 | println(nodes.mkString("\n"))
81 | println("unique address " + cluster.selfUniqueAddress)
82 | nodes.filter(address => address != cluster.selfUniqueAddress).map{address => println("sending to this address: " + address); context.actorSelection(RootActorPath(address.address) / ("/user/StorageNode" match {case RelativeActorPath(elements) => elements})) ! ReplicaEntry(key, value)}
83 | cache += (key -> value)
84 | //replicator ! Entry(key, value)
85 | sender ! RSSClient.Success(s"[success]::> ${Entry(key, value).toString} successful.")
86 | log.info("{}", cache.mkString("\n", "\n", "\n"))
87 | log.info("Total key value pairs till now {}", cache.size)
88 | /**
89 | val client = sender()
90 | val future = Future {
91 | db.withSession { implicit sx =>
92 | tableWithIdQuery.createIfNotExists
93 | tableWithIdQuery.save(model)
94 | client ! "Done"
95 | }
96 | }
97 | future pipeTo self
98 | //cache += (key -> value)
99 | //log.info(s"entry request ${Entry(key, value)} => [${cache.mkString(", ")}]")
100 | **/
101 | case ReplicaEntry(key, value) => cache += (key -> value)
102 |
103 | case Get(key) =>
104 | log.info("{}", Get(key))
105 | //replicator ! Get(key)
106 | if (cache contains key) {
107 | sender ! RSSClient.Success(cache(key))
108 | } else {
109 | sender ! RSSClient.Error(s"[failure]::> key $key not found")
110 | }
111 | /**
112 | val client = sender()
113 | val future = Future {
114 | db.withSession { implicit sx =>
115 | tableWithIdQuery.tryFindById(key) match {
116 | case Success(model) => client ! model.toString
117 | case Failure(t) => client ! s"couldn't find because ${t.getMessage}"
118 | }
119 | }
120 | }
121 |
122 | future pipeTo self
123 | //sender() ! cache.get(key)
124 | //log.info(s"get request ${Get(key)} => [${cache.mkString(", ")}]")
125 | **/
126 | case Evict(key) =>
127 | //replicator ! Evict(key)
128 | log.info("{}", Evict(key))
129 | if (cache contains key) {
130 | cache -= key
131 | sender() ! RSSClient.Success(s"key $key Successfully deleted.")
132 | } else {
133 | sender() ! RSSClient.Error(s"key $key not found. ")
134 | }
135 | /**
136 | val client = sender()
137 | val future = Future {
138 | db.withSession { implicit sx =>
139 | tableWithIdQuery.deleteById(key)
140 | client ! "Delete Successful"
141 | }
142 | }
143 |
144 | future pipeTo self
145 | //cache -= key
146 | //log.info(s"evict request ${Evict(key)} => [${cache.mkString(", ")}]")
147 | **/
148 | case All(key) =>
149 | log.info("Requesting all keys")
150 | if (cache contains key)
151 | sender ! RSSClient.Success(cache.mkString("\n", "\n", "\n"))
152 | else sender ! RSSClient.Error(s"key $key not found.")
153 | }
154 | }
155 |
156 |
157 | object Starter {
158 | def main(args: Array[String]): Unit = {
159 | val port = if (args.isEmpty) "0" else args(0)
160 | val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port").
161 | withFallback(ConfigFactory.parseString("akka.cluster.roles = [storage]")).
162 | withFallback(ConfigFactory.load())
163 | val system = ActorSystem("ClusterSystem", config)
164 | system.actorOf(Props[StorageNode], name = Constants.StorageNode)
165 | }
166 | }
167 |
168 |
169 |
--------------------------------------------------------------------------------