├── .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 | 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 | 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 | 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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 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 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | 161 | 162 | 163 | 168 | 169 | 170 | 171 | 172 | 173 | 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 | 239 | 240 | 241 | 242 | 245 | 246 | 249 | 250 | 251 | 252 | 255 | 256 | 259 | 260 | 263 | 264 | 267 | 268 | 269 | 270 | 273 | 274 | 277 | 278 | 281 | 282 | 285 | 286 | 287 | 288 | 291 | 292 | 295 | 296 | 299 | 300 | 301 | 302 | 305 | 306 | 309 | 310 | 313 | 314 | 317 | 318 | 319 | 320 | 323 | 324 | 327 | 328 | 331 | 332 | 333 | 334 | 337 | 338 | 341 | 342 | 345 | 346 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 359 | 360 | 361 | 362 | 365 | 366 | 369 | 370 | 371 | 372 | 375 | 376 | 379 | 380 | 383 | 384 | 387 | 388 | 391 | 392 | 395 | 396 | 399 | 400 | 401 | 402 | 405 | 406 | 409 | 410 | 413 | 414 | 417 | 418 | 421 | 422 | 425 | 426 | 429 | 430 | 431 | 432 | 435 | 436 | 439 | 440 | 443 | 444 | 447 | 448 | 451 | 452 | 453 | 454 | 457 | 458 | 461 | 462 | 465 | 466 | 469 | 470 | 473 | 474 | 477 | 478 | 479 | 480 | 483 | 484 | 487 | 488 | 491 | 492 | 495 | 496 | 499 | 500 | 503 | 504 | 507 | 508 | 509 | 510 | 513 | 514 | 517 | 518 | 521 | 522 | 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 | 590 | 591 | 592 | 615 | 616 | 617 | 630 | 631 | 632 | 639 | 642 | 644 | 645 | 646 | 647 | 648 | 649 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 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 | 733 | 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 | 777 | 801 | 802 | 803 | 804 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 1422556688627 813 | 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 | 893 | 896 | 897 | 898 | 900 | 901 | 902 | 904 | 905 | 906 | 907 | 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 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | No facets are configured 1297 | 1298 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | scala-sdk-2.10.4 1309 | 1310 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1.7 1321 | 1322 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | akkadynodb 1333 | 1334 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1.7 1346 | 1347 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | SBT: com.google.code.findbugs:jsr305:1.3.9:jar 1358 | 1359 | 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 | ![To do](https://raw.githubusercontent.com/pamu/AkkaDynoDB/master/images/todo.png) 116 | 117 | Overall AkkaDynoDB looks like this 118 | 119 | ![AkkaDynoDB](https://raw.githubusercontent.com/pamu/AkkaDynoDB/master/images/cluster.png) 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 | ![AkkaDynoDB Node](https://raw.githubusercontent.com/pamu/AkkaDynoDB/master/images/node.png) 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 | ![AkkaDynoDB Node](https://raw.githubusercontent.com/pamu/AkkaDynoDB/master/images/node_detail.png) 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 | ![AkkaDynoDB Node](https://raw.githubusercontent.com/pamu/AkkaDynoDB/master/images/ring.png) 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 | --------------------------------------------------------------------------------