├── .circleci └── config.yml ├── .gitignore ├── .scalafmt.conf ├── README.md ├── api-client └── src │ ├── main │ └── scala │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── threadWeaver │ │ └── api │ │ ├── APIException.scala │ │ ├── ClientSettings.scala │ │ ├── HttpClient.scala │ │ ├── ThreadWeaverClient.scala │ │ ├── ThreadWeaverCommandClient.scala │ │ └── ThreadWeaverQueryClient.scala │ └── test │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── api │ └── ThreadWeaverCommandClientSpec.scala ├── api-server └── src │ └── main │ ├── resources │ ├── application.conf │ ├── common │ │ ├── akka.conf │ │ ├── k8s_probe.conf │ │ ├── kamon.conf │ │ └── thread-weaver.conf │ ├── local-cluster.conf │ ├── local-cluster │ │ └── akka.conf │ ├── local-machine │ │ └── akka.conf │ ├── logback.xml │ ├── production.conf │ └── production │ │ └── akka.conf │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── api │ ├── ClusterWatcher.scala │ ├── HealthCheck.scala │ └── Main.scala ├── build.sbt ├── charts ├── dynamodb │ ├── Chart.yaml │ ├── environments │ │ ├── local-values.yaml │ │ └── prod-values.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── pod.yaml │ │ └── service.yaml │ └── values.yaml ├── mysql │ ├── Chart.yaml │ ├── environments │ │ ├── local-values.yaml │ │ └── prod-values.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── pod.yaml │ │ └── service.yaml │ └── values.yaml ├── thread-weaver-api-server │ ├── .helmignore │ ├── Chart.yaml │ ├── environments │ │ ├── .gitignore │ │ ├── local-values.yaml │ │ └── prod-values.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── deployment.yml │ │ ├── rbac.yml │ │ └── service.yml │ └── values.yaml └── thread-weaver-flyway │ ├── .helmignore │ ├── Chart.yaml │ ├── environments │ ├── .gitignore │ ├── local-values.yaml │ └── prod-values.yaml.tpl │ ├── templates │ ├── _helpers.tpl │ ├── job.yaml │ └── secrets.yaml │ └── values.yaml ├── contracts ├── contract-grpc-proto-interface │ └── src │ │ └── main │ │ └── protobuf │ │ └── thread │ │ ├── command.proto │ │ ├── model.proto │ │ └── query.proto ├── contract-http-proto-interface │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── threadWeaver │ │ └── adaptor │ │ └── http │ │ └── json │ │ ├── AddMessagesRequestJson.scala │ │ ├── AddMessagesResponseJson.scala │ │ ├── CreateThreadRequestJson.scala │ │ ├── CreateThreadResponseJson.scala │ │ ├── DestroyThreadRequestJson.scala │ │ ├── DestroyThreadResponseJson.scala │ │ ├── ErrorsResponseJson.scala │ │ ├── GetThreadAdministratorIdsResponseJson.scala │ │ ├── GetThreadMemberIdsResponseJson.scala │ │ ├── GetThreadMessagesResponseJson.scala │ │ ├── GetThreadResponseJson.scala │ │ ├── GetThreadsResponseJson.scala │ │ ├── JoinAdministratorIdsRequestJson.scala │ │ ├── JoinAdministratorIdsResponseJson.scala │ │ ├── JoinMemberIdsRequestJson.scala │ │ ├── JoinMemberIdsResponseJson.scala │ │ ├── LeaveAdministratorIdsRequestJson.scala │ │ ├── LeaveAdministratorIdsResponseJson.scala │ │ ├── LeaveMemberIdsRequestJson.scala │ │ ├── LeaveMemberIdsResponseJson.scala │ │ ├── RemoveMessagesRequestJson.scala │ │ ├── RemoveMessagesResponseJson.scala │ │ ├── ResponseJson.scala │ │ ├── TextMessage.scala │ │ ├── ThreadJson.scala │ │ └── ThreadMessageJson.scala ├── contract-interface │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── threadWeaver │ │ └── adaptor │ │ ├── aggregates │ │ ├── BaseCommandRequest.scala │ │ ├── ThreadCommonProtocol.scala │ │ ├── typed │ │ │ └── ThreadProtocol.scala │ │ └── untyped │ │ │ └── ThreadProtocol.scala │ │ ├── dao │ │ ├── ThreadAdministratorIdsRecord.scala │ │ ├── ThreadMemberIdsRecord.scala │ │ ├── ThreadMessageRecord.scala │ │ └── ThreadRecord.scala │ │ ├── grpc │ │ └── presenter │ │ │ ├── AddMessagesPresenter.scala │ │ │ ├── CreateThreadPresenter.scala │ │ │ ├── DestroyThreadPresenter.scala │ │ │ ├── JoinAdministratorIdsPresenter.scala │ │ │ ├── JoinMemberIdsPresenter.scala │ │ │ ├── LeaveAdministratorIdsPresenter.scala │ │ │ ├── LeaveMemberIdsPresenter.scala │ │ │ └── RemoveMessagesPresenter.scala │ │ ├── http │ │ ├── controller │ │ │ ├── ThreadCommandController.scala │ │ │ └── ThreadQueryController.scala │ │ ├── presenter │ │ │ ├── AddMessagesPresenter.scala │ │ │ ├── CreateThreadPresenter.scala │ │ │ ├── DestroyThreadPresenter.scala │ │ │ ├── JoinAdministratorIdsPresenter.scala │ │ │ ├── JoinMemberIdsPresenter.scala │ │ │ ├── LeaveAdministratorIdsPresenter.scala │ │ │ ├── LeaveMemberIdsPresenter.scala │ │ │ ├── RemoveMessagesPresenter.scala │ │ │ ├── ThreadMessagePresenter.scala │ │ │ └── ThreadPresenter.scala │ │ └── routes │ │ │ └── RouteNames.scala │ │ ├── presenter │ │ └── Presenter.scala │ │ └── readModelUpdater │ │ ├── ThreadReadModelUpdaterProtocol.scala │ │ ├── ThreadReadModelUpdaterSettings.scala │ │ └── ThreadTag.scala └── contract-use-case │ └── src │ └── main │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── useCase │ ├── AddMessagesUseCase.scala │ ├── CreateThreadUseCase.scala │ ├── DestroyThreadUseCase.scala │ ├── JoinAdministratorIdsUseCase.scala │ ├── JoinMemberIdsUseCase.scala │ ├── LeaveAdministratorIdsUseCase.scala │ ├── LeaveMemberIdsUseCase.scala │ ├── RemoveMessagesUseCase.scala │ ├── ThreadWeaverProtocol.scala │ └── ThreadWeaverUseCase.scala ├── modules ├── domain │ └── src │ │ ├── main │ │ └── scala │ │ │ └── com │ │ │ └── github │ │ │ └── j5ik2o │ │ │ └── threadWeaver │ │ │ └── domain │ │ │ └── model │ │ │ ├── accounts │ │ │ ├── Account.scala │ │ │ ├── AccountId.scala │ │ │ └── AccountName.scala │ │ │ └── threads │ │ │ ├── AdministratorIds.scala │ │ │ ├── MemberIds.scala │ │ │ ├── Message.scala │ │ │ ├── MessageId.scala │ │ │ ├── MessageIds.scala │ │ │ ├── Messages.scala │ │ │ ├── Text.scala │ │ │ ├── Thread.scala │ │ │ ├── ThreadId.scala │ │ │ ├── ThreadRemarks.scala │ │ │ ├── ThreadTitle.scala │ │ │ └── ToAccountIds.scala │ │ └── test │ │ └── scala │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── threadWeave │ │ └── domain │ │ └── model │ │ └── accounts │ │ └── AccountNameSpec.scala ├── infrastructure │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── github │ │ └── j5ik2o │ │ └── threadWeaver │ │ └── infrastructure │ │ └── ulid │ │ └── ULID.scala ├── interface │ ├── native-libs │ │ ├── libsqlite4java-linux-amd64-1.0.392.so │ │ ├── libsqlite4java-linux-i386-1.0.392.so │ │ ├── libsqlite4java-osx-1.0.392.dylib │ │ ├── sqlite4java-win32-x64-1.0.392.dll │ │ └── sqlite4java-win32-x86-1.0.392.dll │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── reference.conf │ │ │ │ └── swagger │ │ │ │ │ ├── favicon-16x16.png │ │ │ │ │ ├── favicon-32x32.png │ │ │ │ │ ├── index.html │ │ │ │ │ ├── oauth2-redirect.html │ │ │ │ │ ├── swagger-ui-bundle.js │ │ │ │ │ ├── swagger-ui-bundle.js.map │ │ │ │ │ ├── swagger-ui-standalone-preset.js │ │ │ │ │ ├── swagger-ui-standalone-preset.js.map │ │ │ │ │ ├── swagger-ui.css │ │ │ │ │ ├── swagger-ui.css.map │ │ │ │ │ ├── swagger-ui.js │ │ │ │ │ └── swagger-ui.js.map │ │ │ └── scala │ │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── j5ik2o │ │ │ │ └── threadWeaver │ │ │ │ └── adaptor │ │ │ │ ├── DISettings.scala │ │ │ │ ├── aggregates │ │ │ │ ├── typed │ │ │ │ │ ├── PersistentThreadAggregate.scala │ │ │ │ │ ├── ShardedThreadAggregates.scala │ │ │ │ │ ├── ShardedThreadAggregatesProxy.scala │ │ │ │ │ ├── ThreadAggregate.scala │ │ │ │ │ └── ThreadAggregates.scala │ │ │ │ └── untyped │ │ │ │ │ ├── ChildActorLookup.scala │ │ │ │ │ ├── PersistentThreadAggregate.scala │ │ │ │ │ ├── Settings.scala │ │ │ │ │ ├── ShardedThreadAggregates.scala │ │ │ │ │ ├── ShardedThreadAggregatesRegion.scala │ │ │ │ │ ├── ThreadAggregate.scala │ │ │ │ │ └── ThreadAggregates.scala │ │ │ │ ├── dao │ │ │ │ └── jdbc │ │ │ │ │ ├── SlickDaoSupport.scala │ │ │ │ │ ├── Thread.scala │ │ │ │ │ ├── ThreadAdministratorIds.scala │ │ │ │ │ ├── ThreadMemberIds.scala │ │ │ │ │ └── ThreadMessage.scala │ │ │ │ ├── das │ │ │ │ └── ThreadDas.scala │ │ │ │ ├── error │ │ │ │ └── InterfaceError.scala │ │ │ │ ├── grpc │ │ │ │ ├── presenter │ │ │ │ │ ├── AddMessagesPresenterImpl.scala │ │ │ │ │ ├── CreateThreadPresenterImpl.scala │ │ │ │ │ ├── DestroyThreadPresenterImpl.scala │ │ │ │ │ ├── JoinAdministratorIdsPresenterImpl.scala │ │ │ │ │ ├── JoinMemberIdsPresenterImpl.scala │ │ │ │ │ ├── LeaveAdministratorIdsPresenterImpl.scala │ │ │ │ │ ├── LeaveMemberIdsPresenterImpl.scala │ │ │ │ │ └── RemoveMessagesPresenterImpl.scala │ │ │ │ ├── service │ │ │ │ │ ├── ThreadCommandServiceImpl.scala │ │ │ │ │ └── ThreadQueryServiceImpl.scala │ │ │ │ └── validator │ │ │ │ │ └── ThreadValidatorSupport.scala │ │ │ │ ├── http │ │ │ │ ├── controller │ │ │ │ │ ├── ThreadCommandControllerImpl.scala │ │ │ │ │ └── ThreadQueryControllerImpl.scala │ │ │ │ ├── directives │ │ │ │ │ ├── MetricsDirective.scala │ │ │ │ │ ├── ThreadValidateDirectives.scala │ │ │ │ │ └── ValidationsRejection.scala │ │ │ │ ├── presenter │ │ │ │ │ ├── AddMessagesPresenterImpl.scala │ │ │ │ │ ├── CreateThreadPresenterImpl.scala │ │ │ │ │ ├── DestroyThreadPresenterImpl.scala │ │ │ │ │ ├── JoinAdministratorIdsPresenterImpl.scala │ │ │ │ │ ├── JoinMemberIdsPresenterImpl.scala │ │ │ │ │ ├── LeaveAdministratorIdsPresenterImpl.scala │ │ │ │ │ ├── LeaveMemberIdsPresenterImpl.scala │ │ │ │ │ ├── RemoveMessagesPresenterImpl.scala │ │ │ │ │ ├── ThreadMessagePresenterImpl.scala │ │ │ │ │ └── ThreadPresenterImpl.scala │ │ │ │ ├── rejections │ │ │ │ │ ├── NotFoundRejection.scala │ │ │ │ │ ├── RejectionHandlers.scala │ │ │ │ │ └── ThreadWeaverRejection.scala │ │ │ │ └── routes │ │ │ │ │ ├── DefaultRequestLoggingFormatter.scala │ │ │ │ │ ├── RequestFormatter.scala │ │ │ │ │ ├── RequestResultFormatter.scala │ │ │ │ │ ├── RouteLogging.scala │ │ │ │ │ └── Routes.scala │ │ │ │ ├── metrics │ │ │ │ └── KamonMetricsReporter.scala │ │ │ │ ├── readModelUpdater │ │ │ │ ├── ShardedThreadReadModelUpdaters.scala │ │ │ │ ├── ShardedThreadReadModelUpdatersRegion.scala │ │ │ │ ├── ThreadReadModelUpdater.scala │ │ │ │ └── ThreadReadModelUpdaters.scala │ │ │ │ ├── routing │ │ │ │ └── ThreadToRMURouter.scala │ │ │ │ ├── serialization │ │ │ │ ├── CirceJsonSerialization.scala │ │ │ │ ├── JournalEventTagPartitioner.scala │ │ │ │ ├── ThreadEventJSONSerializer.scala │ │ │ │ ├── ThreadMessageJSONSerializer.scala │ │ │ │ ├── ThreadTaggingEventAdaptor.scala │ │ │ │ └── json │ │ │ │ │ ├── CreateThreadFailedJson.scala │ │ │ │ │ ├── CreateThreadJson.scala │ │ │ │ │ ├── CreateThreadSucceededJson.scala │ │ │ │ │ └── ThreadCreatedJson.scala │ │ │ │ ├── swagger │ │ │ │ └── SwaggerDocService.scala │ │ │ │ ├── util │ │ │ │ └── PingPong.scala │ │ │ │ └── validator │ │ │ │ ├── ValidateUtils.scala │ │ │ │ ├── Validator.scala │ │ │ │ └── package.scala │ │ ├── multi-jvm │ │ │ ├── resources │ │ │ │ ├── logback-test.xml │ │ │ │ └── reference.conf │ │ │ └── scala │ │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── j5ik2o │ │ │ │ └── threadWeaver │ │ │ │ └── adaptor │ │ │ │ └── aggregates │ │ │ │ ├── DynamoDbConfig.scala │ │ │ │ ├── DynamoDbSpecSupport.scala │ │ │ │ ├── LevelDbConfig.scala │ │ │ │ ├── LevelDbSpecSupport.scala │ │ │ │ ├── typed │ │ │ │ ├── ShardedThreadAggregateOnDynamoDbSpec.scala │ │ │ │ └── ShardedThreadAggregateOnLevelDbSpec.scala │ │ │ │ └── untyped │ │ │ │ ├── ShardedThreadAggregateOnDynamoDbSpec.scala │ │ │ │ └── ShardedThreadAggregateOnLevelDbSpec.scala │ │ └── test │ │ │ ├── resources │ │ │ ├── logback-test.xml │ │ │ └── reference.conf │ │ │ └── scala │ │ │ └── com │ │ │ └── github │ │ │ └── j5ik2o │ │ │ └── threadWeaver │ │ │ └── adaptor │ │ │ ├── DITestSettings.scala │ │ │ ├── aggregates │ │ │ ├── PersistenceCleanup.scala │ │ │ ├── typed │ │ │ │ ├── PersistentThreadAggregateOnDynamoDBSpec.scala │ │ │ │ ├── PersistentThreadAggregateOnLevelDBSpec.scala │ │ │ │ ├── ShardedThreadAggregatesSpec.scala │ │ │ │ ├── ThreadAggregateSpec.scala │ │ │ │ ├── ThreadAggregatesSpec.scala │ │ │ │ └── TypedActorSpecSupport.scala │ │ │ └── untyped │ │ │ │ ├── ActorSpecSupport.scala │ │ │ │ ├── AkkaSpec.scala │ │ │ │ ├── PersistentThreadAggregateOnDynamoDBSpec.scala │ │ │ │ ├── PersistentThreadAggregateOnLevelDBSpec.scala │ │ │ │ ├── ShardedThreadAggregatesSpec.scala │ │ │ │ ├── ThreadAggregateSpec.scala │ │ │ │ └── ThreadAggregatesSpec.scala │ │ │ ├── grpc │ │ │ └── service │ │ │ │ ├── ServiceSpec.scala │ │ │ │ └── ThreadServiceImplSpec.scala │ │ │ ├── http │ │ │ └── controller │ │ │ │ ├── RouteSpec.scala │ │ │ │ └── ThreadControllerImplSpec.scala │ │ │ ├── readModelUpdater │ │ │ ├── ThreadReadModelUpdaterOnDynamoDBSpec.scala │ │ │ └── ThreadReadModelUpdaterOnLevelDBSpec.scala │ │ │ └── util │ │ │ ├── DynamoDBSpecSupport.scala │ │ │ ├── FlywayWithMySQLSpecSupport.scala │ │ │ ├── JdbcSpecSupport.scala │ │ │ ├── RandomPortSupport.scala │ │ │ ├── ScalaCheckSupport.scala │ │ │ ├── ScalaFuturesSpecSupport.scala │ │ │ └── Slick3SpecSupport.scala │ └── templates │ │ └── template.ftl └── use-case │ └── src │ └── main │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── useCase │ ├── DISettings.scala │ ├── typed │ ├── AddMessagesUseCaseTypeImpl.scala │ ├── CreateThreadUseCaseTypeImpl.scala │ ├── DestroyThreadUseCaseTypeImpl.scala │ ├── JoinAdministratorIdsUseCaseTypeImpl.scala │ ├── JoinMemberIdsUseCaseTypeImpl.scala │ ├── LeaveAdministratorIdsUseCaseTypeImpl.scala │ ├── LeaveMemberIdsUseCaseTypeImpl.scala │ └── RemoveMessagesUseCaseTypeImpl.scala │ └── untyped │ ├── AddMessagesUseCaseUntypeImpl.scala │ ├── CreateThreadUseCaseUntypeImpl.scala │ ├── DestroyThreadUseCaseUntypeImpl.scala │ ├── JoinAdministratorIdsUseCaseUntypeImpl.scala │ ├── JoinMemberIdsUseCaseUntypeImpl.scala │ ├── LeaveAdministratorIdsUseCaseUntypeImpl.scala │ ├── LeaveMemberIdsUseCaseUntypeImpl.scala │ ├── RemoveMessagesUseCaseUntypeImpl.scala │ └── UseCaseSupport.scala ├── project ├── Dependencies.scala ├── Settings.scala ├── Utils.scala ├── build.properties └── plugins.sbt ├── read-model-updater └── src │ └── main │ ├── resources │ ├── application.conf │ ├── common │ │ ├── akka.conf │ │ ├── k8s_probe.conf │ │ ├── kamon.conf │ │ └── thread-weaver.conf │ ├── local-cluster.conf │ ├── local-cluster │ │ └── akka.conf │ ├── local-machine │ │ └── akka.conf │ ├── logback.xml │ ├── production.conf │ └── production │ │ └── akka.conf │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── rmu │ ├── ClusterWatcher.scala │ ├── HealthCheck.scala │ └── Main.scala ├── scalastyle-config.xml ├── slide ├── .gitignore ├── domain-models.puml ├── images │ ├── K0001062281.jpg │ ├── actor-tree.svg │ ├── akka-event-sourcing.svg │ ├── arete.html │ ├── arete_files │ │ ├── cb=gapi.loaded_0 │ │ ├── celebrating-50-years-of-pride-6537357791592448.3-s.png │ │ ├── free_transration_online.html │ │ ├── id414706506 │ │ ├── m=NBZ7u,TxZWcc,WgDvvc,Z1Gqqd,aa,aam1T,abd,async,bgd,dBHdve,dvl,ecm,exdc,fEVMic,foot,itm,lr,lu,m,mUpTid,mpck,mu,qik19b,sb_wiz,sf,sonic,spch,tl,tobs,vs,xpd,xz7cCd,zMMxKd │ │ ├── m=Uuupec,r36a9c │ │ ├── rs=AA2YrTvOuLZIEz8epvzJpWYCzzvc74aqcw │ │ ├── rs=ACT90oFtVfH8HGapCMs4PqIfJEKufvZoPQ │ │ ├── saved_resource │ │ ├── saved_resource(1) │ │ ├── saved_resource(2) │ │ ├── saved_resource(3) │ │ ├── saved_resource(4) │ │ ├── saved_resource(5) │ │ ├── saved_resource(6) │ │ ├── search │ │ └── 翻訳 │ ├── clean-architecture.jpeg │ ├── cluster-image.svg │ ├── domain-models.svg │ ├── event-stream.png │ ├── helm.png │ ├── logo-hz.png │ ├── logo-vt.png │ ├── modules.svg │ ├── real-events.png │ ├── same-node-layout.svg │ ├── self-prof.png │ ├── separate-node-layout.svg │ ├── system-diagram.svg │ └── thread-aggregate-tree.svg ├── modules.puml ├── pdf │ └── presentation.pdf ├── presentation.md ├── template │ ├── index.html │ ├── remark.min.js │ └── style.scss └── thread-aggregates.puml └── tools ├── deploy ├── deploy-app.sh ├── deploy-flyway.sh ├── eks │ ├── .gitignore │ ├── dd-agent-values.yaml.default │ ├── deploy-dd-agent.sh │ ├── push-image-to-ecr.sh │ ├── secrets.yaml.default │ ├── test-application.sh │ └── test-management.sh ├── k8s-d4m │ ├── build-image.sh │ ├── deploy-local-db.sh │ ├── migrate-local-db.sh │ ├── secrets.yaml │ ├── test-application.sh │ └── test-management.sh ├── k8s-setup.sh └── rbac-config.yaml ├── dynamodb ├── create-table.sh └── table.json ├── flyway ├── Dockerfile ├── Makefile ├── config.env ├── deploy.env ├── src │ └── test │ │ └── resources │ │ └── db-migration │ │ └── V1__Create_Tables.sql └── version.sh ├── gatling-aggregate-runner └── src │ └── main │ ├── resources │ ├── application.conf │ └── logback.xml │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── gatling │ └── runner │ └── Runner.scala ├── gatling-runner ├── docker-compose.yml └── src │ └── main │ ├── resources │ ├── application.conf │ └── logback.xml │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── gatling │ └── runner │ └── Runner.scala ├── gatling-s3-reporter ├── Dockerfile ├── Makefile ├── config.env ├── deploy.env ├── docker-compose.yml ├── generate-report.sh └── version.sh ├── gatling-test └── src │ └── it │ ├── resources │ ├── gatling.conf │ ├── logback.xml │ └── reference.conf │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── gatling │ └── ThreadSimulation.scala ├── local-dynamodb ├── native-libs │ ├── libsqlite4java-linux-amd64-1.0.392.so │ ├── libsqlite4java-linux-i386-1.0.392.so │ ├── libsqlite4java-osx-1.0.392.dylib │ ├── sqlite4java-win32-x64-1.0.392.dll │ └── sqlite4java-win32-x86-1.0.392.dll └── src │ └── main │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── dynmodb │ └── Main.scala ├── migrate-dynamodb └── src │ └── main │ └── scala │ └── com │ └── github │ └── j5ik2o │ └── threadWeaver │ └── dynmodb │ ├── DynamoDBCreator.scala │ └── Main.scala └── terraform ├── .gitignore ├── dynamodb ├── main.tf ├── output.tf └── variables.tf ├── ecr ├── main.tf ├── output.tf └── variables.tf ├── eks-terraform-apply.sh ├── eks-terraform-destroy.sh ├── eks-terraform-env.sh ├── eks-terraform-env.sh.default ├── eks-terraform-init.sh ├── eks-terraform-plan.sh ├── eks.tfvars.default ├── gatling ├── ecr.tf ├── main.tf └── variables.tf ├── main.tf ├── output.tf └── variables.tf /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | environment: 5 | - TEST_TIME_FACTOR: "2" 6 | - JAVA_OPTS: "-XX:ReservedCodeCacheSize=256M -Xms1g -Xmx3g -Xss2m" 7 | - AWS_REGION: "ap-northeast-1" 8 | machine: true 9 | steps: 10 | - run: 11 | command: | 12 | sudo apt-get update -qq && sudo apt-get install -y libaio1 libevent-dev 13 | sudo apt-get install -y software-properties-common 14 | sudo apt-get install -y openjdk-8-jdk 15 | sudo apt-get update -qq 16 | sudo apt-get install -y maven git 17 | echo "deb https://dl.bintray.com/sbt/debian /" | sudo tee -a /etc/apt/sources.list.d/sbt.list 18 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823 19 | sudo apt-get update -qq 20 | sudo apt-get install sbt 21 | - checkout 22 | - restore_cache: 23 | key: scala-library-dependencies-{{ checksum "build.sbt" }} 24 | - run: sbt test 25 | - save_cache: 26 | paths: [ "~/.sbt/boot", "~/.ivy2", "~/.wixMySQL" ] 27 | key: scala-library-dependencies-{{ checksum "build.sbt" }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .cache-main 4 | .cache-tests 5 | .settings/ 6 | target/ 7 | project/target 8 | .cache 9 | .idea/ 10 | .cache-main 11 | .envrc 12 | bin/ 13 | native 14 | *.pyc 15 | *.pem 16 | *.stackdump 17 | *.tfvars 18 | *.tfstate 19 | *.tfstate.backup 20 | *.deb 21 | *.tgz 22 | *.log 23 | .terraform/ 24 | node_modules/ 25 | dump.rdb 26 | 27 | .DS_Store 28 | .credentials 29 | env.sh 30 | cluster.yaml 31 | eksctl-env.sh 32 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 2.0.0-RC5 2 | style = defaultWithAlign 3 | danglingParentheses = true 4 | indentOperator = spray 5 | includeCurlyBraceInSelectChains = true 6 | maxColumn = 120 7 | rewrite.rules = [RedundantParens, SortImports, PreferCurlyFors] 8 | spaces.inImportCurlyBraces = true 9 | binPack.literalArgumentLists = false 10 | unindentTopLevelOperators = true 11 | optIn.breaksInsideChains = true 12 | newlines.alwaysBeforeTopLevelStatements = true -------------------------------------------------------------------------------- /api-client/src/main/scala/com/github/j5ik2o/threadWeaver/api/APIException.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | class APIException(errorMessages: Seq[String]) extends Exception(errorMessages.mkString(", ")) 4 | -------------------------------------------------------------------------------- /api-client/src/main/scala/com/github/j5ik2o/threadWeaver/api/ClientSettings.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | import akka.http.scaladsl.model.Uri 4 | 5 | final case class ClientSettings( 6 | scheme: String, 7 | host: String, 8 | port: Option[Int] = None, 9 | version: String = "v1", 10 | queueSize: Int, 11 | https: Boolean = false 12 | ) { 13 | def urlString: String = s"$scheme://$host${port.fold("")(p => s":$p")}" 14 | def uri: Uri = Uri.from(scheme = scheme, host = host, port = port.getOrElse(0)) 15 | } 16 | -------------------------------------------------------------------------------- /api-client/src/main/scala/com/github/j5ik2o/threadWeaver/api/ThreadWeaverClient.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | import akka.http.scaladsl.model.HttpResponse 4 | import akka.http.scaladsl.unmarshalling.Unmarshal 5 | import akka.stream.Materializer 6 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ErrorsResponseJson 7 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport 8 | 9 | import scala.concurrent.{ ExecutionContext, Future } 10 | 11 | trait ThreadWeaverClient extends FailFastCirceSupport { 12 | import io.circe.generic.auto._ 13 | protected def handleResponse[A]( 14 | response: HttpResponse 15 | )(f: Unmarshal[HttpResponse] => Future[A])(implicit ec: ExecutionContext, mat: Materializer): Future[A] = { 16 | val unmarshal = Unmarshal(response) 17 | if (response.status.isSuccess()) 18 | f(unmarshal) 19 | else 20 | unmarshal.to[ErrorsResponseJson].flatMap { e => 21 | Future.failed(new APIException(e.error_messages)) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /api-client/src/test/scala/com/github/j5ik2o/threadWeaver/api/ThreadWeaverCommandClientSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | import org.scalatest.FreeSpec 4 | 5 | class ThreadWeaverCommandClientSpec extends FreeSpec {} 6 | -------------------------------------------------------------------------------- /api-server/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("local-machine/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | 8 | -------------------------------------------------------------------------------- /api-server/src/main/resources/common/k8s_probe.conf: -------------------------------------------------------------------------------- 1 | k8s_probe { 2 | host = "localhost" 3 | port = "18080" 4 | port = ${?THREAD_WEAVER_API_SERVER_HTTP_PORT} 5 | path { 6 | liveness = "/live" 7 | readiness = "/ready" 8 | } 9 | } -------------------------------------------------------------------------------- /api-server/src/main/resources/common/thread-weaver.conf: -------------------------------------------------------------------------------- 1 | thread-weaver { 2 | api-server { 3 | host = "0.0.0.0" 4 | http { 5 | port = 8080 6 | port = ${?THREAD_WEAVER_API_SERVER_HTTP_PORT} 7 | } 8 | terminate.duration = 3s 9 | nr-of-shards = 30 10 | nr-of-shards = ${?THREAD_WEAVER_API_NR_OF_SHARDS} 11 | } 12 | 13 | read-model-updater.sql-batch-count = 1 14 | read-model-updater.sql-batch-count = ${?THREAD_WEAVER_READ_MODEL_UPDATER_SQL_BATCH_COUNT} 15 | read-model-updater.thread { 16 | shard-name = "thread" 17 | category = "thread" 18 | num-partition = 1 19 | num-partition = ${?THREAD_WEAVER_READ_MODEL_UPDATER_ROOM_NUM_PARTITION} 20 | } 21 | 22 | } 23 | 24 | slick { 25 | profile = "slick.jdbc.MySQLProfile$" 26 | db { 27 | driver = "com.mysql.jdbc.Driver" 28 | url = "jdbc:mysql://localhost:3306/tw?useSSL=false" 29 | url = ${?THREAD_WEAVER_SLICK_URL} 30 | user = "tw" 31 | user = ${?THREAD_WEAVER_SLICK_USER} 32 | password = "passwd" 33 | password = ${?THREAD_WEAVER_SLICK_PASSWORD} 34 | connectionPool = "HikariCP" 35 | keepAliveConnection = true 36 | properties = { 37 | maximumPoolSize = 64 38 | maximumPoolSize = ${?THREAD_WEAVER_SLICK_MAX_POOL_SIZE} 39 | minimumIdle = 64 40 | minimumIdle = ${?THREAD_WEAVER_SLICK_MIN_IDLE_SIZE} 41 | connectionTimeout = 30 42 | connectionTimeout = ${?THREAD_WEAVER_SLICK_CONNECTION_TIMEOUT} 43 | idleTimeout = 30 44 | idleTimeout = ${?THREAD_WEAVER_SLICK_IDLE_TIMEOUT} 45 | } 46 | poolName = "slick-pool" 47 | poolName = ${?THREAD_WEAVER_SLICK_POOL_NAME} 48 | numThreads = 64 49 | numThreads = ${?THREAD_WEAVER_SLICK_NUM_THREADS} 50 | queueSize = 1000 51 | queueSize = ${?THREAD_WEAVER_SLICK_QUEUE_SIZE} 52 | registerMbeans=true 53 | } 54 | } -------------------------------------------------------------------------------- /api-server/src/main/resources/local-cluster.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("local-cluster/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | -------------------------------------------------------------------------------- /api-server/src/main/resources/production.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("production/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | -------------------------------------------------------------------------------- /api-server/src/main/scala/com/github/j5ik2o/threadWeaver/api/ClusterWatcher.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | import akka.actor.{ Actor, ActorLogging } 4 | import akka.cluster.Cluster 5 | 6 | class ClusterWatcher extends Actor with ActorLogging { 7 | private val cluster = Cluster(context.system) 8 | 9 | override def receive: PartialFunction[Any, Unit] = { 10 | case msg => log.info(s"Cluster ${cluster.selfAddress} >>> " + msg) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /api-server/src/main/scala/com/github/j5ik2o/threadWeaver/api/HealthCheck.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.api 2 | 3 | import akka.actor.ActorSystem 4 | import akka.cluster.{ Cluster, MemberStatus } 5 | import cats.syntax.validated._ 6 | import com.github.everpeace.healthchecks._ 7 | 8 | import scala.concurrent.Future 9 | 10 | object HealthCheck { 11 | 12 | def akka(host: String, port: Int)(implicit system: ActorSystem): HealthCheck = { 13 | import system.dispatcher 14 | asyncHealthCheck("akka-cluster") { 15 | Future { 16 | val cluster = Cluster(system) 17 | val status = cluster.selfMember.status 18 | val result = status == MemberStatus.Up || status == MemberStatus.WeaklyUp 19 | if (result) 20 | healthy 21 | else 22 | "Not Found".invalidNel 23 | 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /charts/dynamodb/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: dynamodb 3 | name: dynamodb 4 | version: 0.1.0 -------------------------------------------------------------------------------- /charts/dynamodb/environments/local-values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: amazon/dynamodb-local 7 | tag: 1.11.475 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: dynamodb 11 | type: NodePort 12 | externalPort: 8000 13 | externalNodePort: 32000 14 | internalPort: 8000 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/dynamodb/environments/prod-values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: amazon/dynamodb-local 7 | tag: 1.11.475 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: dynamodb 11 | type: LoadBalancer 12 | externalPort: 8000 13 | externalNodePort: 32000 14 | internalPort: 8000 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/dynamodb/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} -------------------------------------------------------------------------------- /charts/dynamodb/templates/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{ template "name" . }} 5 | labels: 6 | app: {{ template "name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | containers: 12 | - name: {{ template "name" . }} 13 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 14 | ports: 15 | - name: internal-port 16 | containerPort: {{ .Values.service.internalPort }} 17 | -------------------------------------------------------------------------------- /charts/dynamodb/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "name" . }} 5 | labels: 6 | app: {{ template "name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.externalPort }} 14 | targetPort: {{ .Values.service.internalPort }} 15 | {{ if eq .Values.service.type "NodePort" }}nodePort: {{ .Values.service.externalNodePort }}{{ end }} 16 | protocol: TCP 17 | name: {{ .Values.service.name }} 18 | selector: 19 | app: {{ template "name" . }} 20 | release: {{ .Release.Name }} -------------------------------------------------------------------------------- /charts/dynamodb/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: amazon/dynamodb-local 7 | tag: 1.11.475 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: dynamodb 11 | type: LoadBalancer 12 | externalPort: 8000 13 | externalNodePort: 32000 14 | internalPort: 8000 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/mysql/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: mysql 3 | name: mysql 4 | version: 0.1.0 -------------------------------------------------------------------------------- /charts/mysql/environments/local-values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: mysql 7 | tag: 5.6 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: mysql 11 | type: NodePort 12 | externalPort: 3306 13 | externalNodePort: 30306 14 | internalPort: 3306 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/mysql/environments/prod-values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: mysql 7 | tag: 5.6 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: mysql 11 | type: LoadBalancer 12 | externalPort: 3306 13 | externalNodePort: 30306 14 | internalPort: 3306 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/mysql/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} -------------------------------------------------------------------------------- /charts/mysql/templates/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: {{ template "name" . }} 5 | labels: 6 | app: {{ template "name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | containers: 12 | - name: {{ template "name" . }} 13 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 14 | ports: 15 | - name: internal-port 16 | containerPort: {{ .Values.service.internalPort }} 17 | args: ["--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"] 18 | env: 19 | - name: "MAX_SERVERS" 20 | value: "32" 21 | - name: "ENSEMBLE_NAME" 22 | value: "mysql" 23 | - name: "MYSQL_DATABASE" 24 | value: "tw" 25 | - name: "MYSQL_USER" 26 | value: "tw" 27 | - name: "MYSQL_PASSWORD" 28 | value: "passwd" 29 | - name: "MYSQL_ROOT_PASSWORD" 30 | value: "root" -------------------------------------------------------------------------------- /charts/mysql/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "name" . }} 5 | labels: 6 | app: {{ template "name" . }} 7 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 8 | release: {{ .Release.Name }} 9 | heritage: {{ .Release.Service }} 10 | spec: 11 | type: {{ .Values.service.type }} 12 | ports: 13 | - port: {{ .Values.service.externalPort }} 14 | targetPort: {{ .Values.service.internalPort }} 15 | {{ if eq .Values.service.type "NodePort" }}nodePort: {{ .Values.service.externalNodePort }}{{ end }} 16 | protocol: TCP 17 | name: {{ .Values.service.name }} 18 | selector: 19 | app: {{ template "name" . }} 20 | release: {{ .Release.Name }} -------------------------------------------------------------------------------- /charts/mysql/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | replicaCount: 1 5 | image: 6 | repository: mysql 7 | tag: 5.6 8 | pullPolicy: IfNotPresent 9 | service: 10 | name: mysql 11 | type: LoadBalancer 12 | externalPort: 3306 13 | externalNodePort: 30306 14 | internalPort: 3306 15 | resources: 16 | # We usually recommend not to specify default resources and to leave this as a conscious 17 | # choice for the user. This also increases chances charts run on environments with little 18 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 19 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 20 | requests: {} 21 | # cpu: 500m 22 | # memory: 128Mi 23 | # limits: 24 | # cpu: 500m 25 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | environments/ -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: thread-weaver api-server 3 | name: thread-weaver-api-server 4 | version: 1.0.0 -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/environments/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/environments/local-values.yaml: -------------------------------------------------------------------------------- 1 | envName: development 2 | prodEnabled: false 3 | configResource: local-cluster.conf 4 | jvmHeapMin: 1500m 5 | jvmHeapMax: 1500m 6 | jvmMetaMax: 500m 7 | replicaCount: 3 8 | image: 9 | repository: j5ik2o/thread-weaver-api-server 10 | tag: 1.0.0-SNAPSHOT 11 | pullPolicy: IfNotPresent 12 | service: 13 | type: NodePort 14 | api: 15 | name: api 16 | externalPort: 18080 17 | externalNodePort: 30080 18 | internalPort: 8080 19 | management: 20 | name: management 21 | externalPort: 18558 22 | externalNodePort: 30081 23 | internalPort: 8558 24 | resources: 25 | requests: 26 | cpu: 2 27 | memory: 4Gi 28 | limits: 29 | cpu: 2 30 | memory: 4Gi 31 | slick: 32 | url: jdbc:mysql://mysql:3306/tw?useSSL=false 33 | user: tw 34 | maxPoolSize: 10 35 | minIdleSize: 10 36 | numThreads: 10 37 | queueSize: 1000 38 | connectionTimeout: 30 39 | idleTimeout: 30 40 | dynamodb: 41 | journalTableName: Journal 42 | snapshotTableName: Snapshot 43 | readJournalTableName : Journal -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/environments/prod-values.yaml: -------------------------------------------------------------------------------- 1 | envName: production 2 | prodEnabled: true 3 | configResource: production.conf 4 | jvmHeapMin: 1500m 5 | jvmHeapMax: 1500m 6 | jvmMetaMax: 500m 7 | replicaCount: 3 8 | image: 9 | repository: 738575627980.dkr.ecr.ap-northeast-1.amazonaws.com/j5ik2o/thread-weaver-api-server 10 | tag: latest 11 | pullPolicy: Always 12 | service: 13 | type: LoadBalancer 14 | api: 15 | name: api 16 | externalPort: 8080 17 | internalPort: 8080 18 | management: 19 | name: management 20 | externalPort: 8558 21 | internalPort: 8558 22 | resources: 23 | requests: 24 | cpu: 3 25 | memory: 4Gi 26 | limits: 27 | cpu: 3 28 | memory: 4Gi 29 | slick: 30 | url: jdbc:mysql://j5ik2o-rds-cluster-aurora.cluster-ctywrcabnmgr.ap-northeast-1.rds.amazonaws.com:3306/thread_weaver?useSSL=false 31 | user: root 32 | maxPoolSize: 64 33 | minIdleSize: 32 34 | numThreads: 64 35 | queueSize: 1000 36 | connectionTimeout: 30 37 | idleTimeout: 30 38 | dynamodb: 39 | journalTableName: j5ik2o-thread-weaver-journal 40 | snapshotTableName: j5ik2o-thread-weaver-snapshot 41 | readJournalTableName : j5ik2o-thread-weaver-journal -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/templates/rbac.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: thread-weaver-api-server 5 | namespace: thread-weaver 6 | rules: 7 | - apiGroups: [""] 8 | resources: ["pods"] 9 | verbs: ["get", "watch", "list"] 10 | --- 11 | apiVersion: rbac.authorization.k8s.io/v1 12 | kind: RoleBinding 13 | metadata: 14 | name: thread-weaver-api-server 15 | namespace: thread-weaver 16 | subjects: 17 | - kind: ServiceAccount 18 | name: thread-weaver 19 | namespace: thread-weaver 20 | roleRef: 21 | kind: Role 22 | name: thread-weaver-api-server 23 | apiGroup: rbac.authorization.k8s.io 24 | -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/templates/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "name" . }} 5 | namespace: thread-weaver 6 | annotations: 7 | service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http 8 | labels: 9 | app: {{ template "name" . }} 10 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 11 | release: {{ .Release.Name }} 12 | heritage: {{ .Release.Service }} 13 | spec: 14 | selector: 15 | app: {{ template "name" . }} 16 | release: {{ .Release.Name }} 17 | type: {{ .Values.service.type }} 18 | ports: 19 | - name: api 20 | protocol: TCP 21 | port: {{ .Values.service.api.externalPort }} 22 | targetPort: api 23 | {{ if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.api.externalNodePort))) }} 24 | nodePort: {{ .Values.service.api.externalNodePort }} 25 | {{ end }} 26 | - name: management 27 | protocol: TCP 28 | port: {{ .Values.service.management.externalPort }} 29 | targetPort: management 30 | {{ if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.management.externalNodePort))) }} 31 | nodePort: {{ .Values.service.management.externalNodePort }} 32 | {{ end }} 33 | -------------------------------------------------------------------------------- /charts/thread-weaver-api-server/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for astraea-elasticmq. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | envName: development 5 | prodEnabled: false 6 | configResource: application.conf 7 | jvmHeapMin: 1500m 8 | jvmHeapMax: 1500m 9 | jvmMetaMax: 500m 10 | replicaCount: 3 11 | image: 12 | repository: j5ik2o/thread-weaver-api-server 13 | tag: 1.0.0-SNAPSHOT 14 | pullPolicy: IfNotPresent 15 | service: 16 | type: NodePort 17 | api: 18 | name: api 19 | externalPort: 18080 20 | externalNodePort: 30080 21 | internalPort: 8080 22 | management: 23 | name: management 24 | externalPort: 18558 25 | externalNodePort: 30081 26 | internalPort: 8558 27 | slick: 28 | url: jdbc:mysql://mysql:3306/tw?useSSL=false 29 | user: tw 30 | maxPoolSize: 64 31 | minIdleSize: 64 32 | numThreads: 16 33 | queueSize: 1000 34 | connectionTimeout: 30 35 | idleTimeout: 30 36 | dynamodb: 37 | journalTableName: thread_weaver_journal 38 | snapshotTableName: thread_weaver_snapshot 39 | readJournalTableName: thread_weaver_journal 40 | resources: 41 | # We usually recommend not to specify default resources and to leave this as a conscious 42 | # choice for the user. This also increases chances charts run on environments with little 43 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 44 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 45 | requests: {} 46 | # cpu: 500m 47 | # memory: 128Mi 48 | # limits: 49 | # cpu: 500m 50 | # memory: 512Mi -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | environments/ -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | description: flyway job for thread-weaver 3 | name: thread-weaver-flyway 4 | version: 1.0.0 5 | -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/environments/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | prod-values.yaml -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/environments/local-values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | repository: j5ik2o/thread-weaver-flyway 3 | tag: latest 4 | pullPolicy: IfNotPresent 5 | secrets: 6 | flyway: | 7 | flyway.driver=com.mysql.jdbc.Driver 8 | flyway.url=jdbc:mysql://mysql:3306/tw?useSSL=false 9 | flyway.user=tw 10 | flyway.password=passwd 11 | -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/environments/prod-values.yaml.tpl: -------------------------------------------------------------------------------- 1 | image: 2 | repository: ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/j5ik2o/thread-weaver-flyway 3 | tag: latest 4 | pullPolicy: Always 5 | secrets: 6 | flyway: | 7 | flyway.driver=com.mysql.jdbc.Driver 8 | flyway.url=jdbc:mysql://${FLYWAY_HOST}:${FLYWAY_PORT}/${FLYWAY_DB}?useSSL=false 9 | flyway.user=${FLYWAY_USER} 10 | flyway.password=${FLYWAY_PASSWORD} 11 | -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/templates/job.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: batch/v1 2 | kind: Job 3 | metadata: 4 | name: {{ template "name" . }} 5 | namespace: thread-weaver 6 | labels: 7 | app: {{ template "name" . }} 8 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 9 | release: {{ .Release.Name }} 10 | heritage: {{ .Release.Service }} 11 | spec: 12 | completions: 1 13 | parallelism: 1 14 | backoffLimit: 1 15 | template: 16 | spec: 17 | containers: 18 | - name: {{ template "name" . }} 19 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 20 | imagePullPolicy: {{ .Values.image.pullPolicy }} 21 | workingDir: /flyway 22 | command: ["/flyway/flyway"] 23 | args: ["migrate"] 24 | volumeMounts: 25 | - name: {{ template "name" . }} 26 | mountPath: "/flyway/conf/" 27 | readOnly: true 28 | restartPolicy: Never 29 | volumes: 30 | - name: {{ template "name" . }} 31 | secret: 32 | secretName: {{ template "name" . }} 33 | items: 34 | - key: flyway.conf 35 | path: flyway.conf -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/templates/secrets.yaml: -------------------------------------------------------------------------------- 1 | {{- $root := . -}} 2 | {{- $secret := .Values.secrets.flyway -}} 3 | apiVersion: v1 4 | kind: Secret 5 | metadata: 6 | name: {{ template "name" . }} 7 | namespace: thread-weaver 8 | labels: 9 | app: {{ template "name" . }} 10 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }} 11 | release: {{ .Release.Name }} 12 | heritage: {{ .Release.Service }} 13 | type: Opaque 14 | data: 15 | flyway.conf: {{ tpl (print $secret) $root | b64enc | quote }} 16 | -------------------------------------------------------------------------------- /charts/thread-weaver-flyway/values.yaml: -------------------------------------------------------------------------------- 1 | image: 2 | repository: XXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/j5ik2o/thread-weaver-flyway 3 | tag: latest 4 | pullPolicy: Always 5 | secrets: 6 | flyway: | 7 | flyway.driver=com.mysql.jdbc.Driver 8 | flyway.url=jdbc:mysql://j5ik2o-rds-cluster-aurora.cluster-ctywrcabnmgr.ap-northeast-1.rds.amazonaws.com:3306/thread_weaver?useSSL=false 9 | flyway.user=root 10 | flyway.password= -------------------------------------------------------------------------------- /contracts/contract-grpc-proto-interface/src/main/protobuf/thread/command.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "com.github.j5ik2o.threadWeaver.adaptor.grpc.service"; 5 | option java_outer_classname = "ThreadCommandProto"; 6 | 7 | package thread; 8 | 9 | import "thread/model.proto"; 10 | 11 | service ThreadCommandService { 12 | rpc CreateThread (CreateThreadRequest) returns (CreateThreadResponse) { 13 | } 14 | rpc DestroyThread (DestroyThreadRequest) returns (DestroyThreadResponse) { 15 | } 16 | rpc JoinAdministratorIds (JoinAdministratorIdsRequest) returns (JoinAdministratorIdsResponse) { 17 | } 18 | rpc LeaveAdministratorIds (LeaveAdministratorIdsRequest) returns (LeaveAdministratorIdsResponse) { 19 | } 20 | rpc JoinMemberIds (JoinMemberIdsRequest) returns (JoinMemberIdsResponse) { 21 | } 22 | rpc LeaveMemberIds (LeaveMemberIdsRequest) returns (LeaveMemberIdsResponse) { 23 | } 24 | rpc AddMessages (AddMessagesRequest) returns (AddMessagesResponse) { 25 | } 26 | rpc RemoveMessages (RemoveMessagesRequest) returns (RemoveMessagesResponse) { 27 | } 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /contracts/contract-grpc-proto-interface/src/main/protobuf/thread/query.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "com.github.j5ik2o.threadWeaver.adaptor.grpc.service"; 5 | option java_outer_classname = "ThreadQueryProto"; 6 | 7 | package thread; 8 | 9 | import "thread/model.proto"; 10 | 11 | service ThreadQueryService { 12 | rpc GetThread (GetThreadRequest) returns (GetThreadResponse) {} 13 | rpc GetThreads (GetThreadsRequest) returns (GetThreadsResponse) {} 14 | rpc GetMessages (GetMessagesRequest) returns (GetMessagesResponse) {} 15 | } -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/AddMessagesRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class AddMessagesRequestJson(accountId: String, messages: Seq[TextMessage], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/AddMessagesResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class AddMessagesResponseJson(messageIds: Seq[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/CreateThreadRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class CreateThreadRequestJson( 4 | accountId: String, 5 | parentThreadId: Option[String], 6 | title: String, 7 | remarks: Option[String], 8 | administratorIds: Seq[String], 9 | memberIds: Seq[String], 10 | createAt: Long 11 | ) 12 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/CreateThreadResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class CreateThreadResponseJson(thread_id: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/DestroyThreadRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class DestroyThreadRequestJson(accountId: String, createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/DestroyThreadResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class DestroyThreadResponseJson(threadId: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/ErrorsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class ErrorsResponseJson(error_messages: Seq[String]) extends ResponseJson 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/GetThreadAdministratorIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class GetThreadAdministratorIdsResponseJson(results: Seq[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/GetThreadMemberIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class GetThreadMemberIdsResponseJson(results: Seq[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/GetThreadMessagesResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class GetThreadMessagesResponseJson(result: Seq[ThreadMessageJson], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/GetThreadResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class GetThreadResponseJson(result: ThreadJson, error_messages: Seq[String] = Seq.empty) extends ResponseJson 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/GetThreadsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class GetThreadsResponseJson(results: Seq[ThreadJson], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/JoinAdministratorIdsRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class JoinAdministratorIdsRequestJson(accountId: String, accountIds: Seq[String], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/JoinAdministratorIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class JoinAdministratorIdsResponseJson(threadId: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/JoinMemberIdsRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class JoinMemberIdsRequestJson(accountId: String, accountIds: Seq[String], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/JoinMemberIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class JoinMemberIdsResponseJson(threadId: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/LeaveAdministratorIdsRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class LeaveAdministratorIdsRequestJson(accountId: String, administratorIds: Seq[String], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/LeaveAdministratorIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class LeaveAdministratorIdsResponseJson(threadId: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/LeaveMemberIdsRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class LeaveMemberIdsRequestJson(accountId: String, accountIds: Seq[String], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/LeaveMemberIdsResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class LeaveMemberIdsResponseJson(threadId: Option[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/RemoveMessagesRequestJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class RemoveMessagesRequestJson(accountId: String, messageIds: Seq[String], createAt: Long) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/RemoveMessagesResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class RemoveMessagesResponseJson(messageIds: Seq[String], error_messages: Seq[String] = Seq.empty) 4 | extends ResponseJson 5 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/ResponseJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | trait ResponseJson { 4 | def error_messages: Seq[String] 5 | def isSuccessful: Boolean = error_messages.isEmpty 6 | } 7 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/TextMessage.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class TextMessage(replyMessageId: Option[String], toAccountIds: Seq[String], text: String) 4 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/ThreadJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class ThreadJson( 4 | id: String, 5 | creatorId: String, 6 | parentThreadId: Option[String], 7 | title: String, 8 | remarks: Option[String], 9 | createdAt: Long, 10 | updatedAt: Long 11 | ) 12 | -------------------------------------------------------------------------------- /contracts/contract-http-proto-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/json/ThreadMessageJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.json 2 | 3 | final case class ThreadMessageJson( 4 | id: String, 5 | threadId: String, 6 | senderId: String, 7 | `type`: String, 8 | body: String, 9 | createdAt: Long, 10 | updatedAt: Long 11 | ) 12 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/BaseCommandRequest.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | trait BaseCommandRequest 4 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/ThreadCommonProtocol.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | import java.time.Instant 4 | 5 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 6 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 7 | 8 | object ThreadCommonProtocol { 9 | trait Message 10 | 11 | trait Event extends Message { 12 | def id: ULID 13 | def threadId: ThreadId 14 | def createdAt: Instant 15 | } 16 | 17 | case class Started(id: ULID, threadId: ThreadId, createdAt: Instant) extends Event 18 | 19 | case class Stopped(id: ULID, threadId: ThreadId, createdAt: Instant) extends Event 20 | } 21 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/ThreadAdministratorIdsRecord.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao 2 | 3 | trait ThreadAdministratorIdsRecord { 4 | def id: String 5 | def threadId: String 6 | def accountId: String 7 | def adderId: String 8 | def createdAt: java.time.Instant 9 | def updatedAt: java.time.Instant 10 | } 11 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/ThreadMemberIdsRecord.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao 2 | 3 | trait ThreadMemberIdsRecord { 4 | def id: String 5 | def threadId: String 6 | def accountId: String 7 | def adderId: String 8 | def createdAt: java.time.Instant 9 | def updatedAt: java.time.Instant 10 | } 11 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/ThreadMessageRecord.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao 2 | 3 | trait ThreadMessageRecord { 4 | def id: String 5 | def deleted: Boolean 6 | def threadId: String 7 | def senderId: String 8 | def `type`: String 9 | def body: String 10 | def createdAt: java.time.Instant 11 | def updatedAt: java.time.Instant 12 | } 13 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/ThreadRecord.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao 2 | 3 | trait ThreadRecord { 4 | def id: String 5 | def deleted: Boolean 6 | def sequenceNr: Long 7 | def creatorId: String 8 | def parentId: Option[String] 9 | def title: String 10 | def remarks: Option[String] 11 | def createdAt: java.time.Instant 12 | def updatedAt: java.time.Instant 13 | def removedAt: Option[java.time.Instant] 14 | } 15 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/AddMessagesPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 4 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.AddMessagesResponse 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ AddMessagesResponse => AddMessagesGrpcResponse } 6 | 7 | trait AddMessagesPresenter extends Presenter[AddMessagesResponse, AddMessagesGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/CreateThreadPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.CreateThreadResponse 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ CreateThreadResponse => CreateThreadGrpcResponse } 5 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 6 | 7 | trait CreateThreadPresenter extends Presenter[CreateThreadResponse, CreateThreadGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/DestroyThreadPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ DestroyThreadResponse => DestroyThreadGrpcResponse } 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.DestroyThreadResponse 6 | 7 | trait DestroyThreadPresenter extends Presenter[DestroyThreadResponse, DestroyThreadGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/JoinAdministratorIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.JoinAdministratorIdsResponse 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ 5 | JoinAdministratorIdsResponse => JoinAdministratorIdsGrpcResponse 6 | } 7 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 8 | 9 | trait JoinAdministratorIdsPresenter extends Presenter[JoinAdministratorIdsResponse, JoinAdministratorIdsGrpcResponse] 10 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/JoinMemberIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.JoinMemberIdsResponse 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ JoinMemberIdsResponse => JoinMemberIdsGrpcResponse } 5 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 6 | 7 | trait JoinMemberIdsPresenter extends Presenter[JoinMemberIdsResponse, JoinMemberIdsGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/LeaveAdministratorIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.LeaveAdministratorIdsResponse 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ 5 | LeaveAdministratorIdsResponse => LeaveAdministratorIdsGrpcResponse 6 | } 7 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 8 | 9 | trait LeaveAdministratorIdsPresenter extends Presenter[LeaveAdministratorIdsResponse, LeaveAdministratorIdsGrpcResponse] 10 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/LeaveMemberIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ LeaveMemberIdsResponse => LeaveMemberIdsGrpcResponse } 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.LeaveMemberIdsResponse 6 | 7 | trait LeaveMemberIdsPresenter extends Presenter[LeaveMemberIdsResponse, LeaveMemberIdsGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/RemoveMessagesPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.{ RemoveMessagesResponse => RemoveMessagesGrpcResponse } 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.RemoveMessagesResponse 6 | 7 | trait RemoveMessagesPresenter extends Presenter[RemoveMessagesResponse, RemoveMessagesGrpcResponse] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/AddMessagesPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.AddMessagesResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.AddMessagesResponse 6 | 7 | trait AddMessagesPresenter extends Presenter[AddMessagesResponse, AddMessagesResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/CreateThreadPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.CreateThreadResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.CreateThreadResponse 6 | 7 | trait CreateThreadPresenter extends Presenter[CreateThreadResponse, CreateThreadResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/DestroyThreadPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.DestroyThreadResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.DestroyThreadResponse 6 | 7 | trait DestroyThreadPresenter extends Presenter[DestroyThreadResponse, DestroyThreadResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/JoinAdministratorIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.JoinAdministratorIdsResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.JoinAdministratorIdsResponse 6 | 7 | trait JoinAdministratorIdsPresenter extends Presenter[JoinAdministratorIdsResponse, JoinAdministratorIdsResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/JoinMemberIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.JoinMemberIdsResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.JoinMemberIdsResponse 6 | 7 | trait JoinMemberIdsPresenter extends Presenter[JoinMemberIdsResponse, JoinMemberIdsResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/LeaveAdministratorIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.LeaveAdministratorIdsResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.LeaveAdministratorIdsResponse 6 | 7 | trait LeaveAdministratorIdsPresenter extends Presenter[LeaveAdministratorIdsResponse, LeaveAdministratorIdsResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/LeaveMemberIdsPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.LeaveMemberIdsResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.LeaveMemberIdsResponse 6 | 7 | trait LeaveMemberIdsPresenter extends Presenter[LeaveMemberIdsResponse, LeaveMemberIdsResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/RemoveMessagesPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.RemoveMessagesResponseJson 4 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.RemoveMessagesResponse 6 | 7 | trait RemoveMessagesPresenter extends Presenter[RemoveMessagesResponse, RemoveMessagesResponseJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/ThreadMessagePresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.dao.ThreadMessageRecord 4 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ThreadMessageJson 5 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 6 | 7 | trait ThreadMessagePresenter extends Presenter[ThreadMessageRecord, ThreadMessageJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/ThreadPresenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import com.github.j5ik2o.threadWeaver.adaptor.dao.ThreadRecord 4 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ThreadJson 5 | import com.github.j5ik2o.threadWeaver.adaptor.presenter.Presenter 6 | 7 | trait ThreadPresenter extends Presenter[ThreadRecord, ThreadJson] 8 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/routes/RouteNames.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.routes 2 | 3 | object RouteNames { 4 | final val Base: String = "threads" 5 | final val CreateThread: String = s"/$Base/create" 6 | final def DestroyThread(threadId: String): String = s"/$Base/$threadId/destroy" 7 | final def JoinAdministratorIds(threadId: String, accountId: String): String = 8 | s"/$Base/$threadId/administrator-ids/join" 9 | final def LeaveAdministratorIds(threadId: String): String = s"/$Base/$threadId/administrator-ids/leave" 10 | 11 | final def JoinMemberIds(threadId: String): String = s"/$Base/$threadId/member-ids/join" 12 | final def LeaveMemberIds(threadId: String): String = s"/$Base/$threadId/member-ids/leave" 13 | 14 | final def AddMessages(threadId: String): String = s"/$Base/$threadId/messages/add" 15 | final def RemoveMessages(threadId: String): String = s"/$Base/$threadId/messages/remove" 16 | 17 | final def GetAdministratorIds(threadId: String, accountId: String): String = 18 | s"/$Base/$threadId/administrator-ids?account_id=$accountId" 19 | final def GetMemberIds(threadId: String, accountId: String): String = 20 | s"/$Base/$threadId/member-ids?account_id=$accountId" 21 | 22 | final def GetMessages(threadId: String, accountId: String): String = 23 | s"/$Base/$threadId/messages?account_id=$accountId" 24 | final def GetThreads(accountId: String): String = s"/$Base?account_id=$accountId" 25 | final def GetThread(threadId: String, accountId: String): String = s"/$Base/$threadId?account_id=$accountId" 26 | } 27 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/presenter/Presenter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | 6 | trait Presenter[Res, ResRepr] { 7 | def response: Flow[Res, ResRepr, NotUsed] 8 | } 9 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/readModelUpdater/ThreadReadModelUpdaterProtocol.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater 2 | 3 | import java.time.Instant 4 | 5 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 6 | 7 | object ThreadReadModelUpdaterProtocol { 8 | sealed trait Message 9 | 10 | sealed trait CommandMessage extends Message { 11 | def id: ULID 12 | def createAt: Instant 13 | } 14 | sealed trait CommandRequest extends CommandMessage { 15 | def threadTag: ThreadTag 16 | } 17 | 18 | sealed trait CommandResponse extends CommandMessage { 19 | def requestId: ULID 20 | } 21 | 22 | final case class Start(id: ULID, threadTag: ThreadTag, createAt: Instant) extends CommandRequest 23 | final case class Stop(id: ULID, threadTag: ThreadTag, createAt: Instant) extends CommandRequest 24 | 25 | case object Idle extends CommandRequest { 26 | override def id: ULID = throw new UnsupportedOperationException 27 | override def threadTag: ThreadTag = throw new UnsupportedOperationException 28 | override def createAt: Instant = throw new UnsupportedOperationException 29 | } 30 | 31 | case object Stop extends CommandRequest { 32 | override def id: ULID = throw new UnsupportedOperationException 33 | override def threadTag: ThreadTag = throw new UnsupportedOperationException 34 | override def createAt: Instant = throw new UnsupportedOperationException 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/readModelUpdater/ThreadReadModelUpdaterSettings.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater 2 | 3 | import com.typesafe.config.Config 4 | 5 | case class ThreadReadModelUpdaterSettings(category: String, numPartition: Int, shardName: String) 6 | 7 | object ThreadReadModelUpdaterSettings { 8 | 9 | def fromConfig(config: Config): ThreadReadModelUpdaterSettings = new ThreadReadModelUpdaterSettings( 10 | category = config.getString("thread-weaver.read-model-updater.thread.category"), 11 | numPartition = config.getInt("thread-weaver.read-model-updater.thread.num-partition"), 12 | shardName = config.getString("thread-weaver.read-model-updater.thread.shard-name") 13 | ) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /contracts/contract-interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/readModelUpdater/ThreadTag.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater 2 | 3 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 4 | import com.typesafe.config.Config 5 | 6 | import scala.util.hashing.MurmurHash3 7 | 8 | case class ThreadTag(value: String) 9 | 10 | object ThreadTag { 11 | 12 | def fromThreadId(threadId: ThreadId)(implicit config: Config): ThreadTag = { 13 | val settings = ThreadReadModelUpdaterSettings.fromConfig(config) 14 | val num = Math.abs(MurmurHash3.stringHash(threadId.value.asString)) % settings.numPartition 15 | ThreadTag(s"thread-$num") 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/AddMessagesUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ AddMessages, AddMessagesResponse } 4 | 5 | trait AddMessagesUseCase extends ThreadWeaverUseCase[AddMessages, AddMessagesResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/CreateThreadUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ CreateThread, CreateThreadResponse } 4 | 5 | trait CreateThreadUseCase extends ThreadWeaverUseCase[CreateThread, CreateThreadResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/DestroyThreadUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ DestroyThread, DestroyThreadResponse } 4 | 5 | trait DestroyThreadUseCase extends ThreadWeaverUseCase[DestroyThread, DestroyThreadResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/JoinAdministratorIdsUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ 4 | JoinAdministratorIds, 5 | JoinAdministratorIdsResponse 6 | } 7 | 8 | trait JoinAdministratorIdsUseCase extends ThreadWeaverUseCase[JoinAdministratorIds, JoinAdministratorIdsResponse] 9 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/JoinMemberIdsUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ JoinMemberIds, JoinMemberIdsResponse } 4 | 5 | trait JoinMemberIdsUseCase extends ThreadWeaverUseCase[JoinMemberIds, JoinMemberIdsResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/LeaveAdministratorIdsUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ 4 | LeaveAdministratorIds, 5 | LeaveAdministratorIdsResponse 6 | } 7 | 8 | trait LeaveAdministratorIdsUseCase extends ThreadWeaverUseCase[LeaveAdministratorIds, LeaveAdministratorIdsResponse] 9 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/LeaveMemberIdsUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ LeaveMemberIds, LeaveMemberIdsResponse } 4 | 5 | trait LeaveMemberIdsUseCase extends ThreadWeaverUseCase[LeaveMemberIds, LeaveMemberIdsResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/RemoveMessagesUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ RemoveMessages, RemoveMessagesResponse } 4 | 5 | trait RemoveMessagesUseCase extends ThreadWeaverUseCase[RemoveMessages, RemoveMessagesResponse] 6 | -------------------------------------------------------------------------------- /contracts/contract-use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/ThreadWeaverUseCase.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ ThreadWeaverRequest, ThreadWeaverResponse } 6 | 7 | trait ThreadWeaverUseCase[Req <: ThreadWeaverRequest, Res <: ThreadWeaverResponse] { 8 | def execute: Flow[Req, Res, NotUsed] 9 | } 10 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/accounts/Account.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.accounts 2 | 3 | import java.time.Instant 4 | 5 | final case class Account(id: AccountId, name: AccountName, createdAt: Instant, updatedAt: Instant) { 6 | 7 | def withName(value: AccountName): Account = copy(name = value) 8 | 9 | def withUpdatedAt(value: Instant): Account = copy(updatedAt = value) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/accounts/AccountId.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.accounts 2 | 3 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 4 | 5 | final case class AccountId(value: ULID = ULID()) 6 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/accounts/AccountName.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.accounts 2 | 3 | import cats.implicits._ 4 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountName.{ DomainError, FormatError } 5 | import eu.timepit.refined.W 6 | import eu.timepit.refined.api.RefType.applyRef 7 | import eu.timepit.refined.api.Refined 8 | import eu.timepit.refined.boolean.And 9 | import eu.timepit.refined.collection.Size 10 | import eu.timepit.refined.numeric.Interval 11 | import eu.timepit.refined.string.MatchesRegex 12 | 13 | object AccountName { 14 | 15 | type AsString = 16 | String Refined And[MatchesRegex[W.`"[a-z][a-zA-Z0-9]+"`.T], Size[Interval.Closed[W.`1`.T, W.`255`.T]]] 17 | 18 | sealed trait DomainError 19 | 20 | final case class FormatError(message: String) extends DomainError 21 | 22 | } 23 | 24 | final case class AccountName(breachEncapsulationOfValue: AccountName.AsString) { 25 | 26 | def withSuffix(suffix: String): Either[DomainError, AccountName] = { 27 | applyRef[AccountName.AsString](breachEncapsulationOfValue + suffix) 28 | .leftMap[DomainError](FormatError).map(new AccountName(_)) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/AdministratorIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import cats.Semigroup 4 | import cats.data.NonEmptyList 5 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountId 6 | 7 | final case class AdministratorIds(breachEncapsulationOfValues: NonEmptyList[AccountId]) { 8 | 9 | def contains(value: AccountId): Boolean = 10 | breachEncapsulationOfValues.toList.contains(value) 11 | 12 | def filterNot(other: AdministratorIds): Either[Exception, AdministratorIds] = { 13 | val list = breachEncapsulationOfValues.filterNot(p => other.contains(p)) 14 | if (list.isEmpty) 15 | Left(new IllegalArgumentException("Administrators can not be empty.")) 16 | else 17 | Right(AdministratorIds(list.head, list.tail: _*)) 18 | } 19 | 20 | def valuesAsString: Seq[String] = breachEncapsulationOfValues.toList.map(_.value.asString) 21 | 22 | } 23 | 24 | object AdministratorIds { 25 | 26 | def apply(head: AccountId, tail: List[AccountId]): AdministratorIds = 27 | new AdministratorIds(NonEmptyList.of(head, tail: _*)) 28 | 29 | def apply(head: AccountId, tail: AccountId*): AdministratorIds = 30 | new AdministratorIds(NonEmptyList.of(head, tail: _*)) 31 | 32 | implicit object MessagesSemigroup extends Semigroup[AdministratorIds] { 33 | 34 | override def combine(x: AdministratorIds, y: AdministratorIds): AdministratorIds = 35 | AdministratorIds(x.breachEncapsulationOfValues ::: y.breachEncapsulationOfValues) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/MemberIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import cats.Monoid 4 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountId 5 | 6 | final case class MemberIds(breachEncapsulationOfValues: Vector[AccountId]) { 7 | 8 | def contains(value: AccountId): Boolean = 9 | breachEncapsulationOfValues.contains(value) 10 | 11 | def filterNot(other: MemberIds): MemberIds = { 12 | val list = breachEncapsulationOfValues.filterNot(p => other.contains(p)) 13 | MemberIds(list: _*) 14 | } 15 | 16 | def valuesAsString: Seq[String] = breachEncapsulationOfValues.map(_.value.asString) 17 | 18 | } 19 | 20 | object MemberIds { 21 | 22 | def apply(values: AccountId*): MemberIds = new MemberIds(Vector(values: _*)) 23 | 24 | val empty = MemberIds(Vector.empty) 25 | 26 | implicit val memberIdsMonoid: Monoid[MemberIds] = new Monoid[MemberIds] { 27 | override def empty: MemberIds = MemberIds.empty 28 | 29 | override def combine(x: MemberIds, y: MemberIds): MemberIds = 30 | MemberIds(x.breachEncapsulationOfValues ++ y.breachEncapsulationOfValues) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/Message.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import java.time.Instant 4 | 5 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountId 6 | 7 | sealed trait Message[A] { 8 | def id: MessageId 9 | 10 | def `type`: String 11 | 12 | def body: A 13 | 14 | def senderId: AccountId 15 | 16 | def createdAt: Instant 17 | 18 | def updatedAt: Instant 19 | } 20 | 21 | final case class TextMessage( 22 | id: MessageId, 23 | replyMessageId: Option[MessageId], 24 | toAccountIds: ToAccountIds, 25 | body: Text, 26 | senderId: AccountId, 27 | createdAt: Instant, 28 | updatedAt: Instant 29 | ) extends Message[Text] { 30 | override def `type`: String = "text" 31 | } 32 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/MessageId.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 4 | 5 | final case class MessageId(value: ULID = ULID()) 6 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/MessageIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import cats.Monoid 4 | 5 | final case class MessageIds(breachEncapsulationOfValues: Vector[MessageId]) { 6 | 7 | def contains(value: MessageId): Boolean = 8 | breachEncapsulationOfValues.contains(value) 9 | 10 | def diff(other: MessageIds): MessageIds = 11 | MessageIds(breachEncapsulationOfValues.diff(other.breachEncapsulationOfValues)) 12 | 13 | def valuesAsString: Vector[String] = breachEncapsulationOfValues.map(_.value.asString) 14 | 15 | } 16 | 17 | object MessageIds { 18 | 19 | def apply(values: MessageId*): MessageIds = new MessageIds(Vector(values: _*)) 20 | 21 | val empty = MessageIds(Vector.empty) 22 | 23 | implicit val memberIdsMonoid: Monoid[MessageIds] = new Monoid[MessageIds] { 24 | override def empty: MessageIds = MessageIds.empty 25 | 26 | override def combine(x: MessageIds, y: MessageIds): MessageIds = 27 | MessageIds(x.breachEncapsulationOfValues ++ y.breachEncapsulationOfValues) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/Messages.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import cats.Monoid 4 | 5 | final case class Messages(breachEncapsulationOfValues: Vector[Message[_]]) { 6 | 7 | def size: Int = breachEncapsulationOfValues.size 8 | 9 | def filter(messageIds: MessageIds): Messages = 10 | copy( 11 | breachEncapsulationOfValues = breachEncapsulationOfValues.filter(v => messageIds.contains(v.id)) 12 | ) 13 | 14 | def forall(condition: Message[_] => Boolean): Boolean = 15 | breachEncapsulationOfValues.forall(condition) 16 | 17 | def filterNot(messageIds: MessageIds): Messages = { 18 | copy( 19 | breachEncapsulationOfValues = breachEncapsulationOfValues.filterNot(v => messageIds.contains(v.id)) 20 | ) 21 | } 22 | 23 | def toMessageIds: MessageIds = MessageIds(breachEncapsulationOfValues.map(_.id): _*) 24 | 25 | } 26 | 27 | object Messages { 28 | 29 | def apply(values: Message[_]*): Messages = new Messages(Vector(values: _*)) 30 | 31 | val empty = Messages(Vector.empty) 32 | 33 | implicit val messagesMonoid: Monoid[Messages] = new Monoid[Messages] { 34 | override def empty: Messages = Messages.empty 35 | 36 | override def combine(x: Messages, y: Messages): Messages = 37 | Messages(x.breachEncapsulationOfValues ++ y.breachEncapsulationOfValues) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/Text.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | final case class Text(value: String) { 4 | override def toString: String = value 5 | } 6 | 7 | object Text { 8 | 9 | def parseFrom(text: String): Either[Exception, Text] = 10 | if (text.size > 255) 11 | Left(new IllegalArgumentException("too long text")) 12 | else 13 | Right(Text(text)) 14 | } 15 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/ThreadId.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 4 | 5 | final case class ThreadId(value: ULID = ULID()) 6 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/ThreadRemarks.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | final case class ThreadRemarks(value: String) 4 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/ThreadTitle.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | case class ThreadTitle(value: String) 4 | -------------------------------------------------------------------------------- /modules/domain/src/main/scala/com/github/j5ik2o/threadWeaver/domain/model/threads/ToAccountIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.domain.model.threads 2 | 3 | import cats.Monoid 4 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountId 5 | 6 | final case class ToAccountIds(breachEncapsulationOfValues: Vector[AccountId]) 7 | 8 | object ToAccountIds { 9 | 10 | def apply(values: AccountId*): ToAccountIds = new ToAccountIds(Vector(values: _*)) 11 | 12 | val empty = ToAccountIds(Vector.empty) 13 | 14 | implicit val toAccountIdsMonoid: Monoid[ToAccountIds] = new Monoid[ToAccountIds] { 15 | override def empty: ToAccountIds = ToAccountIds.empty 16 | 17 | override def combine(x: ToAccountIds, y: ToAccountIds): ToAccountIds = 18 | ToAccountIds(x.breachEncapsulationOfValues ++ y.breachEncapsulationOfValues) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /modules/domain/src/test/scala/com/github/j5ik2o/threadWeave/domain/model/accounts/AccountNameSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeave.domain.model.accounts 2 | 3 | import com.github.j5ik2o.threadWeaver.domain.model.accounts.AccountName 4 | import eu.timepit.refined.auto._ 5 | import org.scalatest.FreeSpec 6 | 7 | class AccountNameSpec extends FreeSpec { 8 | "AccountName" - { 9 | "check" in { 10 | val result = AccountName("abcdef").withSuffix("_2") 11 | println(result) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/infrastructure/src/main/scala/com/github/j5ik2o/threadWeaver/infrastructure/ulid/ULID.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.infrastructure.ulid 2 | 3 | import de.huxhorn.sulky.ulid.ULID.Value 4 | import de.huxhorn.sulky.ulid.{ ULID => ULIDGen } 5 | import eu.timepit.refined.api.{ Refined, Validate } 6 | 7 | import scala.util.Try 8 | 9 | object ULID { 10 | 11 | private val gen = new ULIDGen() 12 | 13 | def parseFromString(text: String): Try[ULID] = Try { 14 | apply(ULIDGen.parseULID(text)) 15 | } 16 | 17 | case class ULIDType() 18 | 19 | def apply(value: Value = ULID.gen.nextValue()): ULID = new ULID(value) 20 | 21 | type ULIDStringType = String Refined ULIDType 22 | 23 | implicit def ulidValidate: Validate.Plain[String, ULIDType] = 24 | Validate.fromPartial( 25 | s => require(parseFromString(s).isSuccess), 26 | "ULID", 27 | ULIDType() 28 | ) 29 | } 30 | 31 | final case class ULID private (private val value: Value) extends Ordered[ULID] { 32 | 33 | def timestamp: Long = value.timestamp() 34 | def increment: ULID = new ULID(value.increment()) 35 | 36 | def mostSignificantBits: Long = value.getMostSignificantBits 37 | def leastSignificantBits: Long = value.getLeastSignificantBits 38 | 39 | def asString: String = value.toString 40 | def asBytes: Array[Byte] = value.toBytes 41 | 42 | override def compare(that: ULID): Int = 43 | value.compareTo(that.value) 44 | 45 | override def equals(other: Any): Boolean = other match { 46 | case that: ULID => value == that.value 47 | case _ => false 48 | } 49 | 50 | override def hashCode(): Int = { 51 | val state = Seq(value) 52 | state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /modules/interface/native-libs/libsqlite4java-linux-amd64-1.0.392.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/native-libs/libsqlite4java-linux-amd64-1.0.392.so -------------------------------------------------------------------------------- /modules/interface/native-libs/libsqlite4java-linux-i386-1.0.392.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/native-libs/libsqlite4java-linux-i386-1.0.392.so -------------------------------------------------------------------------------- /modules/interface/native-libs/libsqlite4java-osx-1.0.392.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/native-libs/libsqlite4java-osx-1.0.392.dylib -------------------------------------------------------------------------------- /modules/interface/native-libs/sqlite4java-win32-x64-1.0.392.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/native-libs/sqlite4java-win32-x64-1.0.392.dll -------------------------------------------------------------------------------- /modules/interface/native-libs/sqlite4java-win32-x86-1.0.392.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/native-libs/sqlite4java-win32-x86-1.0.392.dll -------------------------------------------------------------------------------- /modules/interface/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | loggers = ["akka.event.slf4j.Slf4jLogger"] 4 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 5 | http.server.preview.enable-http2 = on 6 | } 7 | 8 | passivate-timeout = 60 seconds 9 | 10 | -------------------------------------------------------------------------------- /modules/interface/src/main/resources/swagger/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/src/main/resources/swagger/favicon-16x16.png -------------------------------------------------------------------------------- /modules/interface/src/main/resources/swagger/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/modules/interface/src/main/resources/swagger/favicon-32x32.png -------------------------------------------------------------------------------- /modules/interface/src/main/resources/swagger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /modules/interface/src/main/resources/swagger/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""} -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/typed/ShardedThreadAggregatesProxy.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ ActorRef, Behavior } 5 | import akka.cluster.sharding.typed.ShardingEnvelope 6 | import akka.cluster.sharding.typed.scaladsl.ClusterSharding 7 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.ThreadCommonProtocol 8 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed.ThreadProtocol.CommandRequest 9 | 10 | import scala.concurrent.duration.FiniteDuration 11 | 12 | object ShardedThreadAggregatesProxy { 13 | 14 | def behavior( 15 | clusterSharding: ClusterSharding, 16 | receiveTimeout: FiniteDuration, 17 | subscribers: Seq[ActorRef[ThreadCommonProtocol.Message]] 18 | ): Behavior[CommandRequest] = 19 | Behaviors.setup[CommandRequest] { _ => 20 | val actorRef = ShardedThreadAggregates.initEntityActor(clusterSharding, receiveTimeout, subscribers) 21 | Behaviors.receiveMessagePartial[CommandRequest] { 22 | case msg => 23 | actorRef ! ShardingEnvelope(msg.threadId.value.asString, msg) 24 | Behaviors.same 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/typed/ThreadAggregates.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed 2 | 3 | import akka.actor.typed.scaladsl.Behaviors 4 | import akka.actor.typed.{ ActorRef, Behavior } 5 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.ThreadCommonProtocol 6 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed.ThreadProtocol.CommandRequest 7 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 8 | 9 | object ThreadAggregates { 10 | 11 | val name = "threads" 12 | 13 | def behavior( 14 | subscribers: Seq[ActorRef[ThreadCommonProtocol.Message]], 15 | name: ThreadId => String 16 | )( 17 | behaviorF: (ThreadId, Seq[ActorRef[ThreadCommonProtocol.Message]]) => Behavior[CommandRequest] 18 | ): Behavior[CommandRequest] = { 19 | Behaviors.setup { ctx => 20 | def createAndSend(threadId: ThreadId): ActorRef[CommandRequest] = { 21 | ctx.child(name(threadId)) match { 22 | case None => ctx.spawn(behaviorF(threadId, subscribers), name = name(threadId)) 23 | case Some(ref) => ref.asInstanceOf[ActorRef[CommandRequest]] 24 | } 25 | } 26 | Behaviors.receiveMessage[CommandRequest] { msg => 27 | createAndSend(msg.threadId) ! msg 28 | Behaviors.same 29 | } 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/ChildActorLookup.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | 3 | import akka.actor.{ Actor, ActorContext, ActorLogging, ActorRef, Props } 4 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.BaseCommandRequest 5 | 6 | trait ChildActorLookup extends ActorLogging { this: Actor => 7 | 8 | implicit def context: ActorContext 9 | 10 | type ID 11 | 12 | protected def childName(childId: ID): String 13 | protected def childProps(childId: ID): Props 14 | protected def toChildId(commandRequest: BaseCommandRequest): ID 15 | 16 | protected def forwardToActor: Actor.Receive = { 17 | case cmd: BaseCommandRequest => 18 | context 19 | .child(childName(toChildId(cmd))) 20 | .fold(createAndForward(cmd, toChildId(cmd)))(forwardCommand(cmd)) 21 | } 22 | 23 | protected def forwardCommand(cmd: BaseCommandRequest)(childRef: ActorRef): Unit = 24 | childRef forward cmd 25 | 26 | protected def createAndForward(cmd: BaseCommandRequest, childId: ID): Unit = { 27 | createActor(childId) forward cmd 28 | } 29 | 30 | protected def createActor(childId: ID): ActorRef = 31 | context.actorOf(childProps(childId), childName(childId)) 32 | } 33 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/Settings.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor._ 6 | 7 | import com.typesafe.config.Config 8 | 9 | object Settings extends ExtensionId[Settings] with ExtensionIdProvider { 10 | override def createExtension(system: ExtendedActorSystem): Settings = new Settings(system.settings.config) 11 | 12 | override def lookup(): ExtensionId[_ <: Extension] = Settings 13 | } 14 | 15 | class Settings(config: Config) extends Extension { 16 | def this(system: ExtendedActorSystem) = this(system.settings.config) 17 | 18 | val passivateTimeout = Duration(config.getString("passivate-timeout")) 19 | 20 | } 21 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/ShardedThreadAggregatesRegion.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | 3 | import akka.actor.{ ActorRef, ActorSystem } 4 | import akka.cluster.sharding.{ ClusterSharding, ClusterShardingSettings } 5 | 6 | object ShardedThreadAggregatesRegion { 7 | 8 | def startClusterSharding(nrOfShards: Int, subscribers: Seq[ActorRef])( 9 | implicit system: ActorSystem 10 | ): ActorRef = 11 | ClusterSharding(system).start( 12 | ShardedThreadAggregates.shardName, 13 | ShardedThreadAggregates.props(subscribers, PersistentThreadAggregate.props), 14 | ClusterShardingSettings(system), 15 | ShardedThreadAggregates.extractEntityId, 16 | ShardedThreadAggregates.extractShardId(nrOfShards) 17 | ) 18 | 19 | def shardRegion(implicit system: ActorSystem): ActorRef = 20 | ClusterSharding(system).shardRegion(ShardedThreadAggregates.shardName) 21 | } 22 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/ThreadAggregates.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | 3 | import akka.actor.{ Actor, ActorLogging, ActorRef, Props } 4 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.BaseCommandRequest 5 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 6 | 7 | object ThreadAggregates { 8 | 9 | val name = "threads" 10 | 11 | def props(subscribers: Seq[ActorRef], propsF: ThreadId => Seq[ActorRef] => Props): Props = 12 | Props(new ThreadAggregates(subscribers, propsF)) 13 | 14 | } 15 | 16 | class ThreadAggregates(subscribers: Seq[ActorRef], propsF: ThreadId => Seq[ActorRef] => Props) 17 | extends Actor 18 | with ActorLogging 19 | with ChildActorLookup { 20 | override def receive: Receive = forwardToActor 21 | 22 | override type ID = ThreadId 23 | 24 | override protected def childName(childId: ThreadId): String = childId.value.asString 25 | 26 | override protected def childProps(childId: ThreadId): Props = propsF(childId)(subscribers) 27 | 28 | override protected def toChildId(commandRequest: BaseCommandRequest): ThreadId = 29 | commandRequest.asInstanceOf[ThreadProtocol.CommandRequest].threadId 30 | } 31 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/jdbc/SlickDaoSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao.jdbc 2 | 3 | import java.time.Instant 4 | 5 | import slick.ast.BaseTypedType 6 | import slick.jdbc.JdbcType 7 | 8 | trait SlickDaoSupport { 9 | 10 | val profile: slick.jdbc.JdbcProfile 11 | 12 | import profile.api._ 13 | 14 | implicit def instantColumnType: JdbcType[Instant] with BaseTypedType[Instant] = 15 | MappedColumnType.base[Instant, java.sql.Timestamp]( 16 | { instant => 17 | new java.sql.Timestamp(instant.toEpochMilli) 18 | }, { ts => 19 | Instant.ofEpochMilli(ts.getTime) 20 | } 21 | ) 22 | 23 | trait Record 24 | 25 | trait SoftDeletableRecord extends Record { 26 | val deleted: Boolean 27 | } 28 | 29 | abstract class TableBase[T](_tableTag: Tag, _tableName: String, _schemaName: Option[String] = None) 30 | extends Table[T](_tableTag, _schemaName, _tableName) 31 | 32 | trait SoftDeletableTableSupport[T] { this: TableBase[T] => 33 | def deleted: Rep[Boolean] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/jdbc/ThreadAdministratorIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao.jdbc 2 | 3 | import slick.lifted.ProvenShape 4 | import slick.lifted.PrimaryKey 5 | import com.github.j5ik2o.threadWeaver.adaptor.dao._ 6 | 7 | trait ThreadAdministratorIdsComponent extends SlickDaoSupport { 8 | 9 | import profile.api._ 10 | 11 | case class ThreadAdministratorIdsRecordImpl( 12 | id: String, 13 | threadId: String, 14 | accountId: String, 15 | adderId: String, 16 | createdAt: java.time.Instant, 17 | updatedAt: java.time.Instant 18 | ) extends Record 19 | with ThreadAdministratorIdsRecord 20 | 21 | case class ThreadAdministratorIdss(tag: Tag) 22 | extends TableBase[ThreadAdministratorIdsRecordImpl](tag, "thread_administrator_ids") { 23 | def id: Rep[String] = column[String]("id") 24 | def threadId: Rep[String] = column[String]("thread_id") 25 | def accountId: Rep[String] = column[String]("account_id") 26 | def adderId: Rep[String] = column[String]("adder_id") 27 | def createdAt: Rep[java.time.Instant] = column[java.time.Instant]("created_at") 28 | def updatedAt: Rep[java.time.Instant] = column[java.time.Instant]("updated_at") 29 | def pk: PrimaryKey = primaryKey("pk", (id)) 30 | override def * : ProvenShape[ThreadAdministratorIdsRecordImpl] = 31 | (id, threadId, accountId, adderId, createdAt, updatedAt) <> (ThreadAdministratorIdsRecordImpl.tupled, ThreadAdministratorIdsRecordImpl.unapply) 32 | } 33 | 34 | object ThreadAdministratorIdsDao extends TableQuery(ThreadAdministratorIdss) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/jdbc/ThreadMemberIds.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao.jdbc 2 | 3 | import slick.lifted.ProvenShape 4 | import slick.lifted.PrimaryKey 5 | import com.github.j5ik2o.threadWeaver.adaptor.dao._ 6 | 7 | trait ThreadMemberIdsComponent extends SlickDaoSupport { 8 | 9 | import profile.api._ 10 | 11 | case class ThreadMemberIdsRecordImpl( 12 | id: String, 13 | threadId: String, 14 | accountId: String, 15 | adderId: String, 16 | createdAt: java.time.Instant, 17 | updatedAt: java.time.Instant 18 | ) extends Record 19 | with ThreadMemberIdsRecord 20 | 21 | case class ThreadMemberIdss(tag: Tag) extends TableBase[ThreadMemberIdsRecordImpl](tag, "thread_member_ids") { 22 | def id: Rep[String] = column[String]("id") 23 | def threadId: Rep[String] = column[String]("thread_id") 24 | def accountId: Rep[String] = column[String]("account_id") 25 | def adderId: Rep[String] = column[String]("adder_id") 26 | def createdAt: Rep[java.time.Instant] = column[java.time.Instant]("created_at") 27 | def updatedAt: Rep[java.time.Instant] = column[java.time.Instant]("updated_at") 28 | def pk: PrimaryKey = primaryKey("pk", (id)) 29 | override def * : ProvenShape[ThreadMemberIdsRecordImpl] = 30 | (id, threadId, accountId, adderId, createdAt, updatedAt) <> (ThreadMemberIdsRecordImpl.tupled, ThreadMemberIdsRecordImpl.unapply) 31 | } 32 | 33 | object ThreadMemberIdsDao extends TableQuery(ThreadMemberIdss) 34 | 35 | } 36 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/dao/jdbc/ThreadMessage.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.dao.jdbc 2 | 3 | import slick.lifted.ProvenShape 4 | import slick.lifted.PrimaryKey 5 | import com.github.j5ik2o.threadWeaver.adaptor.dao._ 6 | 7 | trait ThreadMessageComponent extends SlickDaoSupport { 8 | 9 | import profile.api._ 10 | 11 | case class ThreadMessageRecordImpl( 12 | id: String, 13 | deleted: Boolean, 14 | threadId: String, 15 | senderId: String, 16 | `type`: String, 17 | body: String, 18 | createdAt: java.time.Instant, 19 | updatedAt: java.time.Instant 20 | ) extends SoftDeletableRecord 21 | with ThreadMessageRecord 22 | 23 | case class ThreadMessages(tag: Tag) 24 | extends TableBase[ThreadMessageRecordImpl](tag, "thread_message") 25 | with SoftDeletableTableSupport[ThreadMessageRecordImpl] { 26 | def id: Rep[String] = column[String]("id") 27 | def deleted: Rep[Boolean] = column[Boolean]("deleted") 28 | def threadId: Rep[String] = column[String]("thread_id") 29 | def senderId: Rep[String] = column[String]("sender_id") 30 | def `type`: Rep[String] = column[String]("type") 31 | def body: Rep[String] = column[String]("body") 32 | def createdAt: Rep[java.time.Instant] = column[java.time.Instant]("created_at") 33 | def updatedAt: Rep[java.time.Instant] = column[java.time.Instant]("updated_at") 34 | def pk: PrimaryKey = primaryKey("pk", (id)) 35 | override def * : ProvenShape[ThreadMessageRecordImpl] = 36 | (id, deleted, threadId, senderId, `type`, body, createdAt, updatedAt) <> (ThreadMessageRecordImpl.tupled, ThreadMessageRecordImpl.unapply) 37 | } 38 | 39 | object ThreadMessageDao extends TableQuery(ThreadMessages) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/error/InterfaceError.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.error 2 | 3 | sealed trait InterfaceError { 4 | val message: String 5 | val cause: Option[Throwable] 6 | } 7 | 8 | case class ULIDFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 9 | case class ThreadIdFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 10 | case class ThreadTitleFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 11 | case class ThreadRemarksFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 12 | case class AdministratorIdsError(message: String, cause: Option[Throwable] = None) extends InterfaceError 13 | case class AccountIdFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 14 | case class InstantFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 15 | case class TextMessageFormatError(message: String, cause: Option[Throwable] = None) extends InterfaceError 16 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/AddMessagesPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.AddMessagesResponse 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ AddMessagesFailed, AddMessagesSucceeded } 7 | 8 | class AddMessagesPresenterImpl extends AddMessagesPresenter { 9 | override def response: Flow[ThreadWeaverProtocol.AddMessagesResponse, AddMessagesResponse, NotUsed] = 10 | Flow[ThreadWeaverProtocol.AddMessagesResponse].map { 11 | case s: AddMessagesSucceeded => 12 | AddMessagesResponse(isSuccessful = true, s.messageIds.valuesAsString, Seq.empty) 13 | case f: AddMessagesFailed => 14 | AddMessagesResponse(isSuccessful = false, Seq.empty, Seq(f.message)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/CreateThreadPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.CreateThreadResponse 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 7 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ CreateThreadFailed, CreateThreadSucceeded } 8 | 9 | class CreateThreadPresenterImpl extends CreateThreadPresenter { 10 | override def response: Flow[ThreadWeaverProtocol.CreateThreadResponse, CreateThreadResponse, NotUsed] = 11 | Flow[ThreadWeaverProtocol.CreateThreadResponse].map { 12 | case s: CreateThreadSucceeded => 13 | CreateThreadResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 14 | case f: CreateThreadFailed => 15 | CreateThreadResponse(isSuccessful = false, "", Seq(f.message)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/DestroyThreadPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.DestroyThreadResponse 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ DestroyThreadFailed, DestroyThreadSucceeded } 7 | 8 | class DestroyThreadPresenterImpl extends DestroyThreadPresenter { 9 | override def response: Flow[ThreadWeaverProtocol.DestroyThreadResponse, DestroyThreadResponse, NotUsed] = 10 | Flow[ThreadWeaverProtocol.DestroyThreadResponse].map { 11 | case s: DestroyThreadSucceeded => 12 | DestroyThreadResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 13 | case f: DestroyThreadFailed => 14 | DestroyThreadResponse(isSuccessful = false, "", Seq(f.message)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/JoinAdministratorIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.JoinAdministratorIdsResponse 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 7 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ 8 | JoinAdministratorIdsFailed, 9 | JoinAdministratorIdsSucceeded 10 | } 11 | 12 | class JoinAdministratorIdsPresenterImpl extends JoinAdministratorIdsPresenter { 13 | override def response 14 | : Flow[ThreadWeaverProtocol.JoinAdministratorIdsResponse, JoinAdministratorIdsResponse, NotUsed] = 15 | Flow[ThreadWeaverProtocol.JoinAdministratorIdsResponse].map { 16 | case s: JoinAdministratorIdsSucceeded => 17 | JoinAdministratorIdsResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 18 | case f: JoinAdministratorIdsFailed => 19 | JoinAdministratorIdsResponse(isSuccessful = false, "", Seq(f.message)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/JoinMemberIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.JoinMemberIdsResponse 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 7 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ JoinMemberIdsFailed, JoinMemberIdsSucceeded } 8 | 9 | class JoinMemberIdsPresenterImpl extends JoinMemberIdsPresenter { 10 | override def response: Flow[ThreadWeaverProtocol.JoinMemberIdsResponse, JoinMemberIdsResponse, NotUsed] = 11 | Flow[ThreadWeaverProtocol.JoinMemberIdsResponse].map { 12 | case s: JoinMemberIdsSucceeded => 13 | JoinMemberIdsResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 14 | case f: JoinMemberIdsFailed => 15 | JoinMemberIdsResponse(isSuccessful = false, "", Seq(f.message)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/LeaveAdministratorIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.LeaveAdministratorIdsResponse 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 7 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ 8 | LeaveAdministratorIdsFailed, 9 | LeaveAdministratorIdsSucceeded 10 | } 11 | 12 | class LeaveAdministratorIdsPresenterImpl extends LeaveAdministratorIdsPresenter { 13 | override def response 14 | : Flow[ThreadWeaverProtocol.LeaveAdministratorIdsResponse, LeaveAdministratorIdsResponse, NotUsed] = 15 | Flow[ThreadWeaverProtocol.LeaveAdministratorIdsResponse].map { 16 | case s: LeaveAdministratorIdsSucceeded => 17 | LeaveAdministratorIdsResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 18 | case f: LeaveAdministratorIdsFailed => 19 | LeaveAdministratorIdsResponse(isSuccessful = false, "", Seq(f.message)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/LeaveMemberIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.LeaveMemberIdsResponse 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 7 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ LeaveMemberIdsFailed, LeaveMemberIdsSucceeded } 8 | 9 | class LeaveMemberIdsPresenterImpl extends LeaveMemberIdsPresenter { 10 | override def response: Flow[ThreadWeaverProtocol.LeaveMemberIdsResponse, LeaveMemberIdsResponse, NotUsed] = 11 | Flow[ThreadWeaverProtocol.LeaveMemberIdsResponse].map { 12 | case s: LeaveMemberIdsSucceeded => 13 | LeaveMemberIdsResponse(isSuccessful = true, s.threadId.value.asString, Seq.empty) 14 | case f: LeaveMemberIdsFailed => 15 | LeaveMemberIdsResponse(isSuccessful = false, "", Seq(f.message)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/grpc/presenter/RemoveMessagesPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.grpc.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.grpc.model.RemoveMessagesResponse 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ RemoveMessagesFailed, RemoveMessagesSucceeded } 7 | 8 | class RemoveMessagesPresenterImpl extends RemoveMessagesPresenter { 9 | override def response: Flow[ThreadWeaverProtocol.RemoveMessagesResponse, RemoveMessagesResponse, NotUsed] = 10 | Flow[ThreadWeaverProtocol.RemoveMessagesResponse].map { 11 | case s: RemoveMessagesSucceeded => 12 | RemoveMessagesResponse(isSuccessful = true, s.messageIds.valuesAsString, Seq.empty) 13 | case f: RemoveMessagesFailed => 14 | RemoveMessagesResponse(isSuccessful = false, Seq.empty, Seq(f.message)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/directives/ValidationsRejection.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.directives 2 | 3 | import cats.data.NonEmptyList 4 | import com.github.j5ik2o.threadWeaver.adaptor.error.InterfaceError 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.rejections.ThreadWeaverRejection 6 | 7 | case class ValidationsRejection(errors: NonEmptyList[InterfaceError]) extends ThreadWeaverRejection { 8 | override val message: String = errors.toList.map(_.message).mkString(",") 9 | override val cause: Option[InterfaceError] = None 10 | } 11 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/AddMessagesPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.AddMessagesResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 7 | 8 | private[adaptor] class AddMessagesPresenterImpl extends AddMessagesPresenter { 9 | 10 | override def response: Flow[AddMessagesResponse, AddMessagesResponseJson, NotUsed] = Flow[AddMessagesResponse].map { 11 | case f: AddMessagesFailed => 12 | AddMessagesResponseJson( 13 | messageIds = Seq.empty, 14 | error_messages = Seq(f.message) 15 | ) 16 | case s: AddMessagesSucceeded => 17 | AddMessagesResponseJson( 18 | messageIds = s.messageIds.valuesAsString, 19 | error_messages = Seq.empty 20 | ) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/CreateThreadPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.CreateThreadResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 7 | 8 | private[adaptor] class CreateThreadPresenterImpl extends CreateThreadPresenter { 9 | 10 | override def response: Flow[CreateThreadResponse, CreateThreadResponseJson, NotUsed] = 11 | Flow[CreateThreadResponse].map { 12 | case f: CreateThreadFailed => 13 | CreateThreadResponseJson( 14 | thread_id = None, 15 | error_messages = Seq(f.message) 16 | ) 17 | case s: CreateThreadSucceeded => 18 | CreateThreadResponseJson( 19 | thread_id = Some(s.threadId.value.asString), 20 | error_messages = Seq.empty 21 | ) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/DestroyThreadPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.DestroyThreadResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol.{ 7 | DestroyThreadFailed, 8 | DestroyThreadResponse, 9 | DestroyThreadSucceeded 10 | } 11 | 12 | class DestroyThreadPresenterImpl extends DestroyThreadPresenter { 13 | 14 | override def response: Flow[DestroyThreadResponse, DestroyThreadResponseJson, NotUsed] = 15 | Flow[DestroyThreadResponse].map { 16 | case f: DestroyThreadFailed => 17 | DestroyThreadResponseJson( 18 | threadId = None, 19 | error_messages = Seq(f.message) 20 | ) 21 | case s: DestroyThreadSucceeded => 22 | DestroyThreadResponseJson( 23 | threadId = Some(s.threadId.value.asString), 24 | error_messages = Seq.empty 25 | ) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/JoinAdministratorIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.JoinAdministratorIdsResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 7 | 8 | private[adaptor] class JoinAdministratorIdsPresenterImpl extends JoinAdministratorIdsPresenter { 9 | 10 | override def response: Flow[JoinAdministratorIdsResponse, JoinAdministratorIdsResponseJson, NotUsed] = { 11 | Flow[JoinAdministratorIdsResponse].map { 12 | case f: JoinAdministratorIdsFailed => 13 | JoinAdministratorIdsResponseJson(threadId = None, error_messages = Seq(f.message)) 14 | case s: JoinAdministratorIdsSucceeded => 15 | JoinAdministratorIdsResponseJson(threadId = Some(s.threadId.value.asString), error_messages = Seq.empty) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/JoinMemberIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.JoinMemberIdsResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 7 | 8 | private[adaptor] class JoinMemberIdsPresenterImpl extends JoinMemberIdsPresenter { 9 | 10 | override def response: Flow[JoinMemberIdsResponse, JoinMemberIdsResponseJson, NotUsed] = { 11 | Flow[JoinMemberIdsResponse].map { 12 | case f: JoinMemberIdsFailed => 13 | JoinMemberIdsResponseJson(threadId = None, error_messages = Seq(f.message)) 14 | case s: JoinMemberIdsSucceeded => 15 | JoinMemberIdsResponseJson(threadId = Some(s.threadId.value.asString), error_messages = Seq.empty) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/LeaveAdministratorIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.LeaveAdministratorIdsResponseJson 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 6 | 7 | private[adaptor] class LeaveAdministratorIdsPresenterImpl extends LeaveAdministratorIdsPresenter { 8 | 9 | override def response: Flow[LeaveAdministratorIdsResponse, LeaveAdministratorIdsResponseJson, NotUsed] = 10 | Flow[LeaveAdministratorIdsResponse].map { 11 | case f: LeaveAdministratorIdsFailed => 12 | LeaveAdministratorIdsResponseJson(threadId = None, error_messages = Seq(f.message)) 13 | case s: LeaveAdministratorIdsSucceeded => 14 | LeaveAdministratorIdsResponseJson(threadId = Some(s.threadId.value.asString), error_messages = Seq.empty) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/LeaveMemberIdsPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.LeaveMemberIdsResponseJson 5 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 6 | 7 | private[adaptor] class LeaveMemberIdsPresenterImpl extends LeaveMemberIdsPresenter { 8 | 9 | override def response: Flow[LeaveMemberIdsResponse, LeaveMemberIdsResponseJson, NotUsed] = 10 | Flow[LeaveMemberIdsResponse].map { 11 | case f: LeaveMemberIdsFailed => 12 | LeaveMemberIdsResponseJson(threadId = None, error_messages = Seq(f.message)) 13 | case s: LeaveMemberIdsSucceeded => 14 | LeaveMemberIdsResponseJson(threadId = Some(s.threadId.value.asString), error_messages = Seq.empty) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/RemoveMessagesPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | 3 | import akka.NotUsed 4 | import akka.stream.scaladsl.Flow 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.RemoveMessagesResponseJson 6 | import com.github.j5ik2o.threadWeaver.useCase.ThreadWeaverProtocol._ 7 | 8 | private[adaptor] class RemoveMessagesPresenterImpl extends RemoveMessagesPresenter { 9 | 10 | override def response: Flow[RemoveMessagesResponse, RemoveMessagesResponseJson, NotUsed] = 11 | Flow[RemoveMessagesResponse].map { 12 | case f: RemoveMessagesFailed => 13 | RemoveMessagesResponseJson( 14 | messageIds = Seq.empty, 15 | error_messages = Seq(f.message) 16 | ) 17 | case s: RemoveMessagesSucceeded => 18 | RemoveMessagesResponseJson( 19 | messageIds = s.messageIds.valuesAsString, 20 | error_messages = Seq.empty 21 | ) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/ThreadMessagePresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.dao.ThreadMessageRecord 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ThreadMessageJson 6 | 7 | class ThreadMessagePresenterImpl extends ThreadMessagePresenter { 8 | override def response: Flow[ThreadMessageRecord, ThreadMessageJson, NotUsed] = Flow[ThreadMessageRecord].map { 9 | messageRecord => 10 | ThreadMessageJson( 11 | messageRecord.id, 12 | messageRecord.threadId, 13 | messageRecord.senderId, 14 | messageRecord.`type`, 15 | messageRecord.body, 16 | messageRecord.createdAt.toEpochMilli, 17 | messageRecord.updatedAt.toEpochMilli 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/presenter/ThreadPresenterImpl.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.presenter 2 | import akka.NotUsed 3 | import akka.stream.scaladsl.Flow 4 | import com.github.j5ik2o.threadWeaver.adaptor.dao.ThreadRecord 5 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ThreadJson 6 | 7 | class ThreadPresenterImpl extends ThreadPresenter { 8 | 9 | override def response: Flow[ThreadRecord, ThreadJson, NotUsed] = Flow[ThreadRecord].map { threadRecord => 10 | ThreadJson( 11 | threadRecord.id, 12 | threadRecord.creatorId, 13 | threadRecord.parentId, 14 | threadRecord.title, 15 | threadRecord.remarks, 16 | threadRecord.createdAt.toEpochMilli, 17 | threadRecord.updatedAt.toEpochMilli 18 | ) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/rejections/NotFoundRejection.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.rejections 2 | import com.github.j5ik2o.threadWeaver.adaptor.error.InterfaceError 3 | 4 | case class NotFoundRejection(override val message: String, override val cause: Option[InterfaceError]) 5 | extends ThreadWeaverRejection 6 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/rejections/RejectionHandlers.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.rejections 2 | 3 | import akka.http.scaladsl.model.StatusCodes 4 | import akka.http.scaladsl.server.Directives.complete 5 | import akka.http.scaladsl.server.RejectionHandler 6 | import com.github.j5ik2o.threadWeaver.adaptor.http.directives.ValidationsRejection 7 | import com.github.j5ik2o.threadWeaver.adaptor.http.json.ErrorsResponseJson 8 | import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._ 9 | import io.circe.generic.auto._ 10 | 11 | object RejectionHandlers { 12 | 13 | final val default: RejectionHandler = RejectionHandler 14 | .newBuilder() 15 | .handle { 16 | case NotFoundRejection(msg, _) => 17 | complete((StatusCodes.NotFound, ErrorsResponseJson(Seq(msg)))) 18 | case ValidationsRejection(errors) => 19 | complete((StatusCodes.BadRequest, ErrorsResponseJson(errors.map(_.message).toList))) 20 | } 21 | .result() 22 | 23 | } 24 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/rejections/ThreadWeaverRejection.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.rejections 2 | 3 | import akka.http.javadsl.server.CustomRejection 4 | import com.github.j5ik2o.threadWeaver.adaptor.error.InterfaceError 5 | 6 | trait ThreadWeaverRejection extends CustomRejection { 7 | val message: String 8 | val cause: Option[InterfaceError] 9 | protected def withCauseMessage = s"$message${cause.fold("")(v => s": ${v.message}")}" 10 | } 11 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/routes/RequestFormatter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.routes 2 | 3 | import akka.http.scaladsl.model.HttpRequest 4 | import akka.http.scaladsl.server.directives.LogEntry 5 | 6 | trait RequestFormatter { 7 | def formatRequest(request: HttpRequest): LogEntry 8 | } 9 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/routes/RequestResultFormatter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.routes 2 | 3 | import akka.http.scaladsl.model.HttpRequest 4 | import akka.http.scaladsl.server.RouteResult 5 | import akka.http.scaladsl.server.directives.LogEntry 6 | 7 | trait RequestResultFormatter { 8 | def formatRequestResponse(request: HttpRequest): RouteResult => Option[LogEntry] 9 | } 10 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/routes/RouteLogging.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.routes 2 | 3 | import akka.http.scaladsl.server.Directive0 4 | import akka.http.scaladsl.server.directives.DebuggingDirectives 5 | 6 | class RouteLogging()(implicit requestFormatter: RequestFormatter, requestResultFormatter: RequestResultFormatter) { 7 | 8 | val httpLogRequest: Directive0 = DebuggingDirectives.logRequest(requestFormatter.formatRequest _) 9 | 10 | val httpLogRequestResult: Directive0 = 11 | DebuggingDirectives.logRequestResult(requestResultFormatter.formatRequestResponse _) 12 | 13 | } 14 | 15 | object RouteLogging { 16 | 17 | def apply()( 18 | implicit requestFormatter: RequestFormatter, 19 | requestResultFormatter: RequestResultFormatter 20 | ): RouteLogging = new RouteLogging() 21 | 22 | val default: RouteLogging = new RouteLogging()( 23 | DefaultRequestLoggingFormatter.requestFormatter, 24 | DefaultRequestLoggingFormatter.requestResultFormatter 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/http/routes/Routes.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.http.routes 2 | 3 | import akka.actor.typed.ActorSystem 4 | import akka.http.scaladsl.model.{ ContentTypes, HttpEntity, HttpResponse } 5 | import akka.http.scaladsl.server.Directives._ 6 | import akka.http.scaladsl.server.Route 7 | import akka.stream.Materializer 8 | import ch.megard.akka.http.cors.scaladsl.CorsDirectives._ 9 | import com.github.j5ik2o.threadWeaver.adaptor.http.controller.{ ThreadCommandController, ThreadQueryController } 10 | import com.github.j5ik2o.threadWeaver.adaptor.http.directives.MetricsDirective 11 | import com.github.j5ik2o.threadWeaver.adaptor.swagger.SwaggerDocService 12 | 13 | class Routes( 14 | val swaggerDocService: SwaggerDocService, 15 | threadCommandController: ThreadCommandController, 16 | threadQueryController: ThreadQueryController 17 | )( 18 | implicit system: ActorSystem[Nothing], 19 | mat: Materializer 20 | ) extends MetricsDirective { 21 | 22 | def root: Route = 23 | cors() { 24 | RouteLogging.default.httpLogRequestResult { 25 | pathEndOrSingleSlash { 26 | get { 27 | index() 28 | } 29 | } ~ path("swagger") { 30 | getFromResource("swagger/index.html") 31 | } ~ getFromResourceDirectory("swagger") ~ apiMetrics { implicit traceContext => 32 | swaggerDocService.routes ~ threadCommandController.toRoutes ~ threadQueryController.toRoutes 33 | } 34 | } 35 | } 36 | 37 | def index(): Route = complete( 38 | HttpResponse( 39 | entity = HttpEntity( 40 | ContentTypes.`text/html(UTF-8)`, 41 | """Wellcome to Thread Weaver API
swagger""" 42 | ) 43 | ) 44 | ) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/readModelUpdater/ShardedThreadReadModelUpdatersRegion.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater 2 | 3 | import akka.actor.{ ActorRef, ActorSystem } 4 | import akka.cluster.sharding.{ ClusterSharding, ClusterShardingSettings } 5 | import com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater.ThreadReadModelUpdater.ReadJournalType 6 | import slick.jdbc.JdbcProfile 7 | 8 | import scala.concurrent.duration.Duration 9 | 10 | object ShardedThreadReadModelUpdatersRegion { 11 | 12 | def startClusterSharding( 13 | receiveTimeout: Duration, 14 | nrOfShards: Int, 15 | readJournal: ReadJournalType, 16 | profile: JdbcProfile, 17 | db: JdbcProfile#Backend#Database, 18 | sqlBatchSize: Long 19 | )(implicit system: ActorSystem): ActorRef = 20 | ClusterSharding(system).start( 21 | ShardedThreadReadModelUpdaters.shardName, 22 | ShardedThreadReadModelUpdaters.props(receiveTimeout, readJournal, profile, db, sqlBatchSize), 23 | ClusterShardingSettings(system), 24 | ShardedThreadReadModelUpdaters.extractEntityId, 25 | ShardedThreadReadModelUpdaters.extractShardId(nrOfShards) 26 | ) 27 | 28 | def shardRegion(implicit system: ActorSystem): ActorRef = 29 | ClusterSharding(system).shardRegion(ShardedThreadReadModelUpdaters.shardName) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/readModelUpdater/ThreadReadModelUpdaters.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater 2 | 3 | import akka.actor.{ Actor, Props } 4 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.BaseCommandRequest 5 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped.ChildActorLookup 6 | import com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater.ThreadReadModelUpdater.ReadJournalType 7 | import slick.jdbc.JdbcProfile 8 | 9 | class ThreadReadModelUpdaters( 10 | readJournal: ReadJournalType, 11 | profile: JdbcProfile, 12 | db: JdbcProfile#Backend#Database, 13 | sqlBatchSize: Long 14 | ) extends Actor 15 | with ChildActorLookup { 16 | override type ID = ThreadTag 17 | override def receive: Receive = forwardToActor 18 | 19 | override protected def childName(childId: ThreadTag): String = childId.value 20 | 21 | override protected def childProps(childId: ThreadTag): Props = 22 | ThreadReadModelUpdater.props(readJournal, profile, db, sqlBatchSize) 23 | 24 | override protected def toChildId(commandRequest: BaseCommandRequest): ThreadTag = 25 | commandRequest.asInstanceOf[ThreadReadModelUpdaterProtocol.CommandRequest].threadTag 26 | 27 | } 28 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/routing/ThreadToRMURouter.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.routing 2 | 3 | import java.time.Instant 4 | 5 | import akka.actor.{ Actor, ActorRef, Props } 6 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.ThreadCommonProtocol 7 | import com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater.{ ThreadReadModelUpdaterProtocol, ThreadTag } 8 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 9 | 10 | object ThreadToRMURouter { 11 | 12 | def props(rmu: ActorRef): Props = Props(new ThreadToRMURouter(rmu)) 13 | 14 | } 15 | 16 | class ThreadToRMURouter(rmu: ActorRef) extends Actor { 17 | implicit val config = context.system.settings.config 18 | 19 | override def receive: Receive = { 20 | case msg: ThreadCommonProtocol.Started => 21 | rmu ! ThreadReadModelUpdaterProtocol.Start(ULID(), ThreadTag.fromThreadId(msg.threadId), Instant.now) 22 | case msg: ThreadCommonProtocol.Stopped => 23 | rmu ! ThreadReadModelUpdaterProtocol.Stop(ULID(), ThreadTag.fromThreadId(msg.threadId), Instant.now) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/JournalEventTagPartitioner.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization 2 | 3 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 4 | 5 | import scala.util.hashing.MurmurHash3 6 | 7 | trait JournalEventTagPartitioner { 8 | 9 | val numPartition: Int 10 | 11 | def partition(threadId: ThreadId): Int = Math.abs(MurmurHash3.stringHash(threadId.value.asString) % numPartition) 12 | 13 | def partitionTagName(threadId: ThreadId, category: String): String = createTagName(partition(threadId), category) 14 | 15 | def createTagName(id: Int, category: String): String = s"${category}-${id}" 16 | } 17 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/ThreadEventJSONSerializer.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization 2 | 3 | import akka.actor.ExtendedActorSystem 4 | import akka.event.{ Logging, LoggingAdapter } 5 | import akka.serialization.SerializerWithStringManifest 6 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped.ThreadProtocol 7 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped.ThreadProtocol.ThreadCreated 8 | 9 | object ThreadEventJSONSerializer { 10 | final val CREATE = ThreadProtocol.ThreadCreated.getClass.getName.stripSuffix("$") 11 | } 12 | 13 | class ThreadEventJSONSerializer(system: ExtendedActorSystem) extends SerializerWithStringManifest { 14 | import com.github.j5ik2o.threadWeaver.adaptor.serialization.json.ThreadCreatedJson._ 15 | import io.circe.generic.auto._ 16 | private implicit val log: LoggingAdapter = Logging.getLogger(system, getClass) 17 | 18 | override def identifier: Int = 50 19 | 20 | override def manifest(o: AnyRef): String = o.getClass.getName 21 | 22 | override def toBinary(o: AnyRef): Array[Byte] = o match { 23 | case orig: ThreadCreated => CirceJsonSerialization.toBinary(orig) 24 | } 25 | 26 | override def fromBinary(bytes: Array[Byte], manifest: String): AnyRef = manifest match { 27 | case ThreadEventJSONSerializer.CREATE => CirceJsonSerialization.fromBinary(bytes) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/ThreadMessageJSONSerializer.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization 2 | 3 | class ThreadMessageJSONSerializer {} 4 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/ThreadTaggingEventAdaptor.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization 2 | 3 | import akka.actor.ExtendedActorSystem 4 | import akka.persistence.journal.{ Tagged, WriteEventAdapter } 5 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.ThreadCommonProtocol 6 | import com.github.j5ik2o.threadWeaver.adaptor.readModelUpdater.ThreadReadModelUpdaterSettings 7 | 8 | class ThreadTaggingEventAdaptor(system: ExtendedActorSystem) extends WriteEventAdapter with JournalEventTagPartitioner { 9 | val settings = ThreadReadModelUpdaterSettings.fromConfig(system.settings.config) 10 | 11 | override val numPartition: Int = settings.numPartition 12 | 13 | override def manifest(event: Any): String = "" 14 | 15 | override def toJournal(event: Any): Any = event match { 16 | case event: ThreadCommonProtocol.Event => 17 | val tag = partitionTagName(event.threadId, settings.category) 18 | Tagged(event, Set(tag)) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/json/CreateThreadFailedJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization.json 2 | 3 | import java.time.Instant 4 | 5 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped.ThreadProtocol.CreateThreadFailed 6 | import com.github.j5ik2o.threadWeaver.adaptor.serialization.DomainObjToJsonReprIso 7 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 8 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 9 | 10 | case class CreateThreadFailedJson(id: String, requestId: String, threadId: String, message: String, createAt: Long) 11 | 12 | object CreateThreadFailedJson { 13 | implicit object CreateThreadFailedIso extends DomainObjToJsonReprIso[CreateThreadFailed, CreateThreadFailedJson] { 14 | override def convertTo(domainObj: CreateThreadFailed): CreateThreadFailedJson = 15 | CreateThreadFailedJson( 16 | id = domainObj.id.asString, 17 | requestId = domainObj.requestId.asString, 18 | threadId = domainObj.threadId.value.asString, 19 | message = domainObj.message, 20 | createAt = domainObj.createAt.toEpochMilli 21 | ) 22 | 23 | override def convertFrom(json: CreateThreadFailedJson): CreateThreadFailed = 24 | (for { 25 | id <- ULID.parseFromString(json.id) 26 | requestId <- ULID.parseFromString(json.requestId) 27 | threadId <- ULID.parseFromString(json.threadId) 28 | } yield (id, requestId, threadId)).fold(throw _, { 29 | case (id, requestId, threadId) => 30 | CreateThreadFailed( 31 | id, 32 | requestId, 33 | ThreadId(threadId), 34 | json.message, 35 | Instant.ofEpochMilli(json.createAt) 36 | ) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/serialization/json/CreateThreadSucceededJson.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.serialization.json 2 | 3 | import java.time.Instant 4 | 5 | import com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped.ThreadProtocol.CreateThreadSucceeded 6 | import com.github.j5ik2o.threadWeaver.adaptor.serialization.DomainObjToJsonReprIso 7 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 8 | import com.github.j5ik2o.threadWeaver.infrastructure.ulid.ULID 9 | 10 | case class CreateThreadSucceededJson(id: String, requestId: String, threadId: String, createAt: Long) 11 | 12 | object CreateThreadSucceededJson { 13 | 14 | implicit object CreateThreadSucceededIso 15 | extends DomainObjToJsonReprIso[CreateThreadSucceeded, CreateThreadSucceededJson] { 16 | override def convertTo(domainObj: CreateThreadSucceeded): CreateThreadSucceededJson = 17 | CreateThreadSucceededJson( 18 | id = domainObj.id.asString, 19 | requestId = domainObj.requestId.asString, 20 | threadId = domainObj.threadId.value.asString, 21 | createAt = domainObj.createAt.toEpochMilli 22 | ) 23 | 24 | override def convertFrom(json: CreateThreadSucceededJson): CreateThreadSucceeded = { 25 | (for { 26 | id <- ULID.parseFromString(json.id) 27 | requestId <- ULID.parseFromString(json.requestId) 28 | threadId <- ULID.parseFromString(json.threadId) 29 | } yield (id, requestId, threadId)).fold(throw _, { 30 | case (id, requestId, threadId) => 31 | CreateThreadSucceeded( 32 | id, 33 | requestId, 34 | ThreadId(threadId), 35 | Instant.ofEpochMilli(json.createAt) 36 | ) 37 | }) 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/swagger/SwaggerDocService.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.swagger 2 | 3 | import com.github.swagger.akka.SwaggerHttpService 4 | import com.github.swagger.akka.model.Info 5 | 6 | class SwaggerDocService(hostName: String, port: Int, val apiClasses: Set[Class[_]]) extends SwaggerHttpService { 7 | override val host: String = s"127.0.0.1:$port" // the url of your api, not swagger's json endpoint 8 | override val apiDocsPath: String = "api-docs" // where you want the swagger-json endpoint exposed 9 | override val info: Info = Info() // provides license and other description details 10 | override val unwantedDefinitions: Seq[String] = Seq("Function1", "Function1RequestContextFutureRouteResult") 11 | } 12 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/util/PingPong.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import akka.actor.typed.{ ActorRef, ActorSystem, Behavior } 4 | import akka.actor.typed.scaladsl.Behaviors 5 | 6 | object PingPong extends App { 7 | trait Message 8 | case class Ping(reply: ActorRef[Message]) extends Message 9 | case object Pong extends Message 10 | 11 | def receiver: Behavior[Message] = 12 | Behaviors.setup[Message] { ctx => 13 | Behaviors.receiveMessagePartial[Message] { 14 | case Ping(replyTo) => 15 | ctx.log.info("ping") 16 | replyTo ! Pong 17 | Behaviors.same 18 | } 19 | } 20 | 21 | def main: Behavior[Message] = Behaviors.setup { ctx => 22 | val receiverRef = ctx.spawn(receiver, name = "receiver") 23 | receiverRef ! Ping(ctx.self) 24 | Behaviors.receiveMessagePartial[Message] { 25 | case Pong => 26 | ctx.log.info("pong") 27 | receiverRef ! Ping(ctx.self) 28 | Behaviors.same 29 | } 30 | } 31 | 32 | ActorSystem(main, "ping-pong") 33 | 34 | } 35 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/validator/Validator.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.validator 2 | 3 | trait Validator[A, B] { 4 | def validate(value: A): ValidationResult[B] 5 | } 6 | -------------------------------------------------------------------------------- /modules/interface/src/main/scala/com/github/j5ik2o/threadWeaver/adaptor/validator/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor 2 | 3 | import cats.data.ValidatedNel 4 | import com.github.j5ik2o.threadWeaver.adaptor.error.InterfaceError 5 | 6 | package object validator { 7 | type ValidationResult[A] = ValidatedNel[InterfaceError, A] 8 | } 9 | -------------------------------------------------------------------------------- /modules/interface/src/multi-jvm/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%X{sourceThread}] [%logger{36}] - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%X{sourceThread}] [%logger{36}] [%X{akkaSource}] - %msg%n 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /modules/interface/src/multi-jvm/resources/reference.conf: -------------------------------------------------------------------------------- 1 | akka.test.timefactor = ${?TEST_TIME_FACTOR} -------------------------------------------------------------------------------- /modules/interface/src/multi-jvm/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/DynamoDbSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | import akka.cluster.Cluster 4 | import akka.remote.testconductor.RoleName 5 | import akka.remote.testkit.{ MultiNodeSpec, MultiNodeSpecCallbacks } 6 | import com.github.j5ik2o.threadWeaver.adaptor.util.DynamoDBSpecSupport 7 | import org.scalatest.{ BeforeAndAfterAll, FreeSpecLike, Matchers } 8 | 9 | trait DynamoDbSpecSupport 10 | extends MultiNodeSpecCallbacks 11 | with FreeSpecLike 12 | with Matchers 13 | with BeforeAndAfterAll 14 | with DynamoDBSpecSupport { 15 | this: MultiNodeSpec => 16 | 17 | import DynamoDbConfig._ 18 | 19 | override protected lazy val dynamoDBPort: Int = 8000 20 | 21 | override def beforeAll(): Unit = multiNodeSpecBeforeAll() 22 | 23 | override def afterAll(): Unit = multiNodeSpecAfterAll() 24 | 25 | def join(from: RoleName, to: RoleName)(f: => Unit): Unit = { 26 | runOn(from) { 27 | Cluster(system) join node(to).address 28 | f 29 | } 30 | enterBarrier(from.name + "-joined") 31 | } 32 | 33 | override protected def atStartup(): Unit = {} 34 | 35 | override protected def afterTermination(): Unit = { 36 | runOn(controller) { 37 | shutdownDynamoDBLocal() 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /modules/interface/src/multi-jvm/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/LevelDbConfig.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | import akka.remote.testkit.MultiNodeConfig 4 | import com.typesafe.config.ConfigFactory 5 | 6 | object LevelDbConfig extends MultiNodeConfig { 7 | val controller = role("controller") 8 | val node1 = role("node1") 9 | val node2 = role("node2") 10 | 11 | testTransport(on = true) 12 | 13 | commonConfig( 14 | ConfigFactory 15 | .parseString( 16 | """ 17 | |akka.loggers = ["akka.event.slf4j.Slf4jLogger"] 18 | |akka.loglevel = "DEBUG" 19 | |akka.logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 20 | |akka.actor.debug.receive = on 21 | | 22 | |akka.cluster.metrics.enabled=off 23 | |akka.actor.provider = "cluster" 24 | | 25 | |thread-weaver { 26 | | read-model-updater.thread { 27 | | shard-name = "thread" 28 | | category = "thread" 29 | | num-partition = 1 30 | | } 31 | |} 32 | | 33 | |akka.persistence.journal.plugin = "akka.persistence.journal.leveldb-shared" 34 | |akka.persistence.journal.leveldb-shared.store { 35 | | native = off 36 | | dir = "target/test-shared-journal" 37 | |} 38 | | 39 | |akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local" 40 | |akka.persistence.snapshot-store.local.dir = "target/test-snapshots" 41 | | 42 | |passivate-timeout = 60 seconds 43 | """.stripMargin 44 | ) 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /modules/interface/src/multi-jvm/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/LevelDbSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | import java.io.File 4 | 5 | import akka.cluster.Cluster 6 | import akka.remote.testconductor.RoleName 7 | import akka.remote.testkit.{ MultiNodeSpec, MultiNodeSpecCallbacks } 8 | import org.apache.commons.io.FileUtils 9 | import org.scalatest.{ BeforeAndAfterAll, FreeSpecLike, Matchers } 10 | 11 | trait LevelDbSpecSupport extends MultiNodeSpecCallbacks with FreeSpecLike with Matchers with BeforeAndAfterAll { 12 | this: MultiNodeSpec => 13 | 14 | import DynamoDbConfig._ 15 | 16 | val storageLocations: List[File] = 17 | List("akka.persistence.journal.leveldb.dir", 18 | "akka.persistence.journal.leveldb-shared.store.dir", 19 | "akka.persistence.snapshot-store.local.dir").map(s => new File(system.settings.config.getString(s))) 20 | 21 | override def beforeAll(): Unit = multiNodeSpecBeforeAll() 22 | 23 | override def afterAll(): Unit = multiNodeSpecAfterAll() 24 | 25 | def join(from: RoleName, to: RoleName)(f: => Unit): Unit = { 26 | runOn(from) { 27 | Cluster(system) join node(to).address 28 | f 29 | } 30 | enterBarrier(from.name + "-joined") 31 | } 32 | 33 | override protected def atStartup(): Unit = { 34 | runOn(controller) { 35 | storageLocations.foreach(dir => FileUtils.deleteDirectory(dir)) 36 | } 37 | } 38 | 39 | override protected def afterTermination(): Unit = { 40 | runOn(controller) { 41 | storageLocations.foreach(dir => FileUtils.deleteDirectory(dir)) 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /modules/interface/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%X{sourceThread}] [%logger{36}] - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%X{sourceThread}] [%logger{36}] [%X{akkaSource}] - %msg%n 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /modules/interface/src/test/resources/reference.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | test.timefactor = ${?TEST_TIME_FACTOR} 3 | loglevel = DEBUG 4 | stdout-loglevel = INFO 5 | loggers = ["akka.event.slf4j.Slf4jLogger"] 6 | logging-filter = "akka.event.slf4j.Slf4jLoggingFilter" 7 | 8 | log-dead-letters = 10 9 | log-dead-letters-during-shutdown = on 10 | 11 | actor { 12 | debug { 13 | # receive = on 14 | # lifecycle = on 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/PersistenceCleanup.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates 2 | 3 | import java.io.File 4 | 5 | import akka.actor.ActorSystem 6 | import org.apache.commons.io.FileUtils 7 | 8 | import scala.util.Try 9 | 10 | trait PersistenceCleanup { 11 | 12 | def storageLocations(implicit system: ActorSystem): Seq[File] = 13 | List( 14 | "akka.persistence.journal.leveldb.dir", 15 | "akka.persistence.journal.leveldb-shared.store.dir", 16 | "akka.persistence.snapshot-store.local.dir" 17 | ).map { s => 18 | new File(system.settings.config.getString(s)) 19 | } 20 | 21 | def deleteStorageLocations(implicit system: ActorSystem): Unit = { 22 | storageLocations.foreach(dir => Try(FileUtils.deleteDirectory(dir))) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/typed/ThreadAggregatesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed 2 | 3 | import akka.actor.typed.ActorRef 4 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 5 | 6 | class ThreadAggregatesSpec extends ThreadAggregateSpec { 7 | 8 | override def newThreadRef(threadId: ThreadId): ActorRef[ThreadProtocol.CommandRequest] = 9 | spawn(ThreadAggregates.behavior(Seq.empty, ThreadAggregate.name)(ThreadAggregate.behavior)) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/typed/TypedActorSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.typed 2 | 3 | import akka.actor.testkit.typed.scaladsl.ActorTestKit 4 | import akka.actor.typed.ActorRef 5 | 6 | trait TypedActorSpecSupport { 7 | 8 | val testKit: ActorTestKit 9 | 10 | def killActors(actors: ActorRef[_]*): Unit = { 11 | actors.foreach { actor => 12 | testKit.stop(actor) 13 | Thread.sleep(1000) // the actor name is not unique intermittently on travis when creating it again after killActors, this is ducktape. 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/ActorSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | 3 | import akka.actor.ActorRef 4 | import akka.testkit.TestKit 5 | 6 | trait ActorSpecSupport { this: TestKit => 7 | 8 | def killActors(actors: ActorRef*): Unit = { 9 | actors.foreach { actor => 10 | watch(actor) 11 | system.stop(actor) 12 | expectTerminated(actor) 13 | Thread.sleep(1000) // the actor name is not unique intermittently on travis when creating it again after killActors, this is ducktape. 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/aggregates/untyped/ThreadAggregatesSpec.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.aggregates.untyped 2 | import akka.actor.ActorRef 3 | import com.github.j5ik2o.threadWeaver.domain.model.threads.ThreadId 4 | 5 | class ThreadAggregatesSpec extends ThreadAggregateSpec { 6 | override def newThreadRef(threadId: ThreadId): ActorRef = 7 | system.actorOf(ThreadAggregates.props(Seq.empty, (ThreadAggregate.props _).curried)) 8 | } 9 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/util/FlywayWithMySQLSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import java.io.File 4 | 5 | import com.github.j5ik2o.scalatestplus.db._ 6 | import com.wix.mysql.distribution.Version._ 7 | import org.scalatest.TestSuite 8 | import org.seasar.util.io.ResourceUtil 9 | 10 | import scala.concurrent.duration._ 11 | 12 | trait FlywayWithMySQLSpecSupport extends FlywayWithMySQLdOneInstancePerSuite { 13 | this: TestSuite => 14 | 15 | override protected lazy val mySQLdConfig: MySQLdConfig = MySQLdConfig( 16 | version = v5_6_21, 17 | port = Some(RandomPortSupport.temporaryServerPort()), 18 | userWithPassword = Some(UserWithPassword("tw", "passwd")), 19 | timeout = Some((30 seconds) * sys.env.getOrElse("SBT_TEST_TIME_FACTOR", "1").toDouble) 20 | ) 21 | 22 | override protected lazy val downloadConfig: DownloadConfig = 23 | super.downloadConfig.copy(cacheDir = new File(sys.env("HOME") + "/.wixMySQL/downloads")) 24 | 25 | override protected lazy val schemaConfigs: Seq[SchemaConfig] = Seq(SchemaConfig(name = "tw")) 26 | 27 | override protected def flywayConfig(jdbcUrl: String): FlywayConfig = { 28 | val buildDir = ResourceUtil.getBuildDir(getClass) 29 | val file = new File(buildDir, "/../../../../../tools/flyway/src/test/resources/db-migration") 30 | FlywayConfig( 31 | locations = Seq( 32 | s"filesystem:${file.getAbsolutePath}" 33 | ), 34 | placeholderConfig = Some( 35 | PlaceholderConfig( 36 | placeholderReplacement = true, 37 | placeholders = Map("engineName" -> "MEMORY", "idSequenceNumberEngineName" -> "MyISAM") 38 | ) 39 | ) 40 | ) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/util/JdbcSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import org.scalatest.concurrent.ScalaFutures 4 | 5 | trait JdbcSpecSupport extends ScalaFutures { 6 | this: FlywayWithMySQLSpecSupport => 7 | val tables: Seq[String] 8 | 9 | def jdbcPort: Int = mySQLdConfig.port.get 10 | 11 | } 12 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/util/RandomPortSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import java.net.InetSocketAddress 4 | import java.nio.channels.ServerSocketChannel 5 | 6 | /** 7 | * This code is originated from Spray. 8 | * https://github.com/spray/spray/blob/b473d9e8ce503bafc72825914f46ae6be1588ce7/spray-util/src/main/scala/spray/util/Utils.scala#L35-L47 9 | */ 10 | trait RandomPortSupport { 11 | 12 | def temporaryServerAddress(interface: String = "127.0.0.1"): InetSocketAddress = { 13 | val serverSocket = ServerSocketChannel.open() 14 | try { 15 | serverSocket.socket.bind(new InetSocketAddress(interface, 0)) 16 | val port = serverSocket.socket.getLocalPort 17 | new InetSocketAddress(interface, port) 18 | } finally serverSocket.close() 19 | } 20 | 21 | def temporaryServerHostnameAndPort(interface: String = "127.0.0.1"): (String, Int) = { 22 | val socketAddress = temporaryServerAddress(interface) 23 | socketAddress.getHostName -> socketAddress.getPort 24 | } 25 | 26 | def temporaryServerPort(interface: String = "127.0.0.1"): Int = 27 | temporaryServerHostnameAndPort(interface)._2 28 | } 29 | 30 | object RandomPortSupport extends RandomPortSupport 31 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/util/ScalaCheckSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import org.scalacheck.Gen 4 | import org.scalatest.prop.PropertyChecks 5 | 6 | trait ScalaCheckSupport extends PropertyChecks { 7 | 8 | def sameAs[A](c: Traversable[A], d: Traversable[A]): Boolean = { 9 | def counts(e: Traversable[A]) = e groupBy identity mapValues (_.size) 10 | counts(c) == counts(d) 11 | } 12 | 13 | val timestampGen: Gen[Long] = 14 | Gen.choose(-24 * 60 * 60 * 1000, 24 * 60 * 60 * 1000).map(_ + System.currentTimeMillis()) 15 | 16 | } 17 | -------------------------------------------------------------------------------- /modules/interface/src/test/scala/com/github/j5ik2o/threadWeaver/adaptor/util/ScalaFuturesSpecSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.adaptor.util 2 | 3 | import org.scalatest.concurrent.ScalaFutures 4 | import org.scalatest.time.{ Seconds, Span } 5 | 6 | trait ScalaFuturesSpecSupport { 7 | this: ScalaFutures => 8 | override implicit def patienceConfig: PatienceConfig = 9 | PatienceConfig(timeout = scaled(Span(180, Seconds)), interval = scaled(Span(3, Seconds))) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /modules/use-case/src/main/scala/com/github/j5ik2o/threadWeaver/useCase/untyped/UseCaseSupport.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.useCase.untyped 2 | 3 | import monix.eval.Task 4 | import org.slf4j.LoggerFactory 5 | 6 | import scala.concurrent.Future 7 | import scala.concurrent.duration._ 8 | 9 | trait UseCaseSupport { 10 | 11 | val maxRetries = 3 12 | val firstDelay = 300 milliseconds 13 | 14 | private val logger = LoggerFactory.getLogger(getClass) 15 | 16 | def retryBackoff[A]( 17 | sourceFuture: Future[A], 18 | maxRetries: Int, 19 | firstDelay: FiniteDuration, 20 | retryMessage: String = "" 21 | ): Task[A] = { 22 | Task.fromFuture(sourceFuture).onErrorHandleWith { 23 | case ex: akka.pattern.AskTimeoutException => 24 | if (maxRetries > 0) { 25 | // Recursive call, it's OK as Monix is stack-safe 26 | logger.info(s"Retrying($maxRetries)...: $retryMessage") 27 | retryBackoff(sourceFuture, maxRetries - 1, firstDelay * 2, retryMessage) 28 | .delayExecution(firstDelay) 29 | } else 30 | Task.raiseError(ex) 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /project/Utils.scala: -------------------------------------------------------------------------------- 1 | object Utils { 2 | import java.net.InetSocketAddress 3 | import java.nio.channels.ServerSocketChannel 4 | 5 | trait RandomPortSupport { 6 | 7 | def temporaryServerAddress(interface: String = "127.0.0.1"): InetSocketAddress = { 8 | val serverSocket = ServerSocketChannel.open() 9 | try { 10 | serverSocket.socket.bind(new InetSocketAddress(interface, 0)) 11 | val port = serverSocket.socket.getLocalPort 12 | new InetSocketAddress(interface, port) 13 | } finally serverSocket.close() 14 | } 15 | 16 | def temporaryServerHostnameAndPort(interface: String = "127.0.0.1"): (String, Int) = { 17 | val socketAddress = temporaryServerAddress(interface) 18 | socketAddress.getHostName -> socketAddress.getPort 19 | } 20 | 21 | def temporaryServerPort(interface: String = "127.0.0.1"): Int = 22 | temporaryServerHostnameAndPort(interface)._2 23 | } 24 | 25 | object RandomPortSupport extends RandomPortSupport 26 | 27 | } 28 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.3.8 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers ++= Seq( 2 | "Sonatype OSS Snapshot Repository" at "https://oss.sonatype.org/content/repositories/snapshots/", 3 | "Sonatype OSS Release Repository" at "https://oss.sonatype.org/content/repositories/releases/", 4 | "Seasar Repository" at "https://maven.seasar.org/maven2/", 5 | Resolver.bintrayRepo("kamon-io", "sbt-plugins") 6 | ) 7 | 8 | libraryDependencies ++= Seq( 9 | "com.h2database" % "h2" % "1.4.195", 10 | "commons-io" % "commons-io" % "2.5", 11 | "org.seasar.util" % "s2util" % "0.0.1", 12 | "com.github.j5ik2o" %% "reactive-aws-ecs-core" % "1.1.3" 13 | ) 14 | 15 | addSbtPlugin("io.kamon" % "sbt-aspectj-runner" % "1.1.0") 16 | 17 | addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % "0.1.4") 18 | 19 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0") 20 | 21 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") 22 | 23 | addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.4.1") 24 | 25 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.4.0") 26 | 27 | addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.10") 28 | 29 | addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2") 30 | 31 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1") 32 | 33 | addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.2") 34 | 35 | addSbtPlugin("io.github.davidmweber" % "flyway-sbt" % "5.2.0") 36 | 37 | addSbtPlugin("jp.co.septeni-original" % "sbt-dao-generator" % "1.0.8") 38 | 39 | addSbtPlugin("com.chatwork" % "sbt-wix-embedded-mysql" % "1.0.9") 40 | 41 | addSbtPlugin("com.lightbend.akka.grpc" % "sbt-akka-grpc" % "0.6.1") 42 | 43 | addSbtPlugin("com.mintbeans" % "sbt-ecr" % "0.14.1") 44 | 45 | addSbtPlugin("org.ensime" % "sbt-ensime" % "2.5.1") 46 | -------------------------------------------------------------------------------- /read-model-updater/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("local-machine/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | 8 | -------------------------------------------------------------------------------- /read-model-updater/src/main/resources/common/k8s_probe.conf: -------------------------------------------------------------------------------- 1 | k8s_probe { 2 | host = "localhost" 3 | port = "18080" 4 | port = ${?THREAD_WEAVER_API_SERVER_HTTP_PORT} 5 | path { 6 | liveness = "/live" 7 | readiness = "/ready" 8 | } 9 | } -------------------------------------------------------------------------------- /read-model-updater/src/main/resources/common/thread-weaver.conf: -------------------------------------------------------------------------------- 1 | thread-weaver { 2 | api-server { 3 | host = "0.0.0.0" 4 | http { 5 | port = 8080 6 | port = ${?THREAD_WEAVER_API_SERVER_HTTP_PORT} 7 | } 8 | terminate.duration = 3s 9 | } 10 | 11 | read-model-updater.sql-batch-count = 1 12 | read-model-updater.sql-batch-count = ${?THREAD_WEAVER_READ_MODEL_UPDATER_SQL_BATCH_COUNT} 13 | read-model-updater { 14 | nr-of-shards = 30 15 | nr-of-shards = ${?THREAD_WEAVER_READ_MODEL_UPDATER_NR_OF_SHARDS} 16 | } 17 | read-model-updater.room { 18 | shard-name = "room" 19 | category = "room" 20 | num-partition = 1 21 | num-partition = ${?THREAD_WEAVER_READ_MODEL_UPDATER_ROOM_NUM_PARTITION} 22 | } 23 | 24 | } 25 | 26 | slick { 27 | profile = "slick.jdbc.MySQLProfile$" 28 | db { 29 | driver = "com.mysql.jdbc.Driver" 30 | url = "jdbc:mysql://localhost:3306/tw?useSSL=false" 31 | url = ${?THREAD_WEAVER_SLICK_URL} 32 | user = "tw" 33 | user = ${?THREAD_WEAVER_SLICK_USER} 34 | password = "passwd" 35 | password = ${?THREAD_WEAVER_SLICK_PASSWORD} 36 | connectionPool = "HikariCP" 37 | keepAliveConnection = true 38 | properties = { 39 | maximumPoolSize = 64 40 | maximumPoolSize = ${?THREAD_WEAVER_SLICK_MAX_POOL_SIZE} 41 | minimumIdle = 64 42 | minimumIdle = ${?THREAD_WEAVER_SLICK_MIN_IDLE_SIZE} 43 | connectionTimeout = 30 44 | connectionTimeout = ${?THREAD_WEAVER_SLICK_CONNECTION_TIMEOUT} 45 | idleTimeout = 30 46 | idleTimeout = ${?THREAD_WEAVER_SLICK_IDLE_TIMEOUT} 47 | } 48 | poolName = "slick-pool" 49 | poolName = ${?THREAD_WEAVER_SLICK_POOL_NAME} 50 | numThreads = 64 51 | numThreads = ${?THREAD_WEAVER_SLICK_NUM_THREADS} 52 | queueSize = 1000 53 | queueSize = ${?THREAD_WEAVER_SLICK_QUEUE_SIZE} 54 | registerMbeans=true 55 | } 56 | } -------------------------------------------------------------------------------- /read-model-updater/src/main/resources/local-cluster.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("local-cluster/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | -------------------------------------------------------------------------------- /read-model-updater/src/main/resources/production.conf: -------------------------------------------------------------------------------- 1 | include classpath("common/akka.conf") 2 | include classpath("production/akka.conf") 3 | include classpath("common/kamon.conf") 4 | include classpath("common/thread-weaver.conf") 5 | include classpath("common/k8s_probe.conf") 6 | include classpath("common/kamon.conf") 7 | -------------------------------------------------------------------------------- /read-model-updater/src/main/scala/com/github/j5ik2o/threadWeaver/rmu/ClusterWatcher.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.rmu 2 | 3 | import akka.actor.{ Actor, ActorLogging } 4 | import akka.cluster.Cluster 5 | 6 | class ClusterWatcher extends Actor with ActorLogging { 7 | private val cluster = Cluster(context.system) 8 | 9 | override def receive: PartialFunction[Any, Unit] = { 10 | case msg => log.info(s"Cluster ${cluster.selfAddress} >>> " + msg) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /read-model-updater/src/main/scala/com/github/j5ik2o/threadWeaver/rmu/HealthCheck.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.rmu 2 | 3 | import akka.actor.ActorSystem 4 | import akka.cluster.{ Cluster, MemberStatus } 5 | import cats.syntax.validated._ 6 | import com.github.everpeace.healthchecks._ 7 | 8 | import scala.concurrent.Future 9 | 10 | object HealthCheck { 11 | 12 | def akka(host: String, port: Int)(implicit system: ActorSystem): HealthCheck = { 13 | import system.dispatcher 14 | asyncHealthCheck("akka-cluster") { 15 | Future { 16 | val cluster = Cluster(system) 17 | val status = cluster.selfMember.status 18 | val result = status == MemberStatus.Up || status == MemberStatus.WeaklyUp 19 | if (result) 20 | healthy 21 | else 22 | "Not Found".invalidNel 23 | 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /slide/.gitignore: -------------------------------------------------------------------------------- 1 | .tmp -------------------------------------------------------------------------------- /slide/domain-models.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | class Thread { 4 | + type Result[A] = Either[ThreadError, A] 5 | + id: ThreadId 6 | 7 | + def joinAdministratorIds(value: AdministratorIds, senderId: AccountId, at: Instant): Result[Thread] 8 | + def leaveAdministratorIds(value: AdministratorIds, senderId: AccountId, at: Instant): Result[Thread] 9 | + def getAdministratorIds(senderId: AccountId): Result[AdministratorIds] 10 | 11 | + def joinMemberIds(value: MemberIds, senderId: AccountId, at: Instant): Result[Thread] 12 | + def leaveMemberIds(value: MemberIds, senderId: AccountId, at: Instant): Result[Thread] 13 | + def getMemberIds(senderId: AccountId): Result[MemberIds] 14 | 15 | + def addMessages(values: Messages, at: Instant): Result[Thread] 16 | + def removeMessages(values: MessageIds, removerId: AccountId, at: Instant): Result[(Thread, MessageIds)] 17 | + def getMessages(senderId: AccountId): Result[Messages] 18 | 19 | + def destroy(senderId: AccountId, at: Instant): Result[Thread] 20 | } 21 | 22 | abstract class ThreadEvent { 23 | + id: ThreadEventId 24 | + threadId: ThreadId 25 | + createAt: Instant 26 | } 27 | 28 | class ThreadCreated extends ThreadEvent 29 | class ThreadDestroyed extends ThreadEvent 30 | class AdministratorIdsJoined extends ThreadEvent 31 | class AdministratorIdsLeft extends ThreadEvent 32 | class MemberIdsJoined extends ThreadEvent 33 | class MemberIdsLeft extends ThreadEvent 34 | class MessagesAdded extends ThreadEvent 35 | class MessagesRemoved extends ThreadEvent 36 | 37 | @enduml -------------------------------------------------------------------------------- /slide/images/K0001062281.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/K0001062281.jpg -------------------------------------------------------------------------------- /slide/images/arete_files/celebrating-50-years-of-pride-6537357791592448.3-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/celebrating-50-years-of-pride-6537357791592448.3-s.png -------------------------------------------------------------------------------- /slide/images/arete_files/free_transration_online.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/free_transration_online.html -------------------------------------------------------------------------------- /slide/images/arete_files/id414706506: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/id414706506 -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(1): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(1) -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(2): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(2) -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(3): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(3) -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(4): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(4) -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(5): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(5) -------------------------------------------------------------------------------- /slide/images/arete_files/saved_resource(6): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/saved_resource(6) -------------------------------------------------------------------------------- /slide/images/arete_files/search: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/search -------------------------------------------------------------------------------- /slide/images/arete_files/翻訳: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/arete_files/翻訳 -------------------------------------------------------------------------------- /slide/images/clean-architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/clean-architecture.jpeg -------------------------------------------------------------------------------- /slide/images/event-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/event-stream.png -------------------------------------------------------------------------------- /slide/images/helm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/helm.png -------------------------------------------------------------------------------- /slide/images/logo-hz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/logo-hz.png -------------------------------------------------------------------------------- /slide/images/logo-vt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/logo-vt.png -------------------------------------------------------------------------------- /slide/images/real-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/real-events.png -------------------------------------------------------------------------------- /slide/images/self-prof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/images/self-prof.png -------------------------------------------------------------------------------- /slide/modules.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | [modules/infrastructure] 3 | 4 | [modules/domain] -right-> [modules/infrastructure] 5 | [modules/use-case] -right-> [modules/infrastructure] 6 | [modules/interface] -right-> [modules/infrastructure] 7 | [applications/api-server] -right-> [modules/infrastructure] 8 | 9 | [modules/use-case] .up.> [contracts/contract-use-case] 10 | [modules/use-case] .up.> [contracts/contract-interface] 11 | [modules/use-case] -up-> [modules/domain] 12 | [modules/interface] -up-> [modules/domain] 13 | [applications/api-server] -up-> [modules/domain] 14 | [contracts/contract-interface] .up.> [contracts/contract-use-case] 15 | [contracts/contract-interface] .up.> [contracts/contract-http-proto-interface] 16 | [modules/interface] .up.> [contracts/contract-interface] 17 | [modules/interface] -up-> [modules/use-case] 18 | [applications/api-server] -up-> [modules/interface] 19 | [modules/api-client] .up.> [contracts/contract-http-proto-interface] 20 | 21 | 22 | @enduml -------------------------------------------------------------------------------- /slide/pdf/presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/slide/pdf/presentation.pdf -------------------------------------------------------------------------------- /slide/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | {{{style}}} 7 | 8 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /slide/thread-aggregates.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | class Thread 4 | 5 | class ThreadAggregate { 6 | val state: Thread 7 | } 8 | 9 | class PersistentThreadAggregate { 10 | val childRef: ActorRef 11 | } 12 | 13 | 14 | class ThreadAggregates { 15 | val childRef: ActorRef 16 | } 17 | 18 | class ShardedThreadAggregates{ 19 | } 20 | 21 | ShardedThreadAggregates -right-|> ThreadAggregates: <> 22 | ThreadAggregate .down.> Thread: <> 23 | PersistentThreadAggregate .down.> ThreadAggregate: <> 24 | ThreadAggregates .down.> PersistentThreadAggregate: <> 25 | 26 | 27 | @enduml -------------------------------------------------------------------------------- /tools/deploy/deploy-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd $(dirname $0) 4 | 5 | if [[ $# == 0 ]]; then 6 | echo "Parameters are empty." 7 | exit 1 8 | fi 9 | 10 | while getopts e: OPT 11 | do 12 | case ${OPT} in 13 | "e") ENV_NAME="$OPTARG" ;; 14 | esac 15 | done 16 | 17 | pushd ../../charts 18 | 19 | KUBE_NAMESPACE=thread-weaver 20 | APP_NAME=thread-weaver-api-server 21 | 22 | helm upgrade ${APP_NAME} ./${APP_NAME} -i -f ${APP_NAME}/environments/${ENV_NAME}-values.yaml --namespace ${KUBE_NAMESPACE} --recreate-pods 23 | 24 | popd -------------------------------------------------------------------------------- /tools/deploy/deploy-flyway.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | 7 | if [[ $# == 0 ]]; then 8 | echo "Parameters are empty." 9 | exit 1 10 | fi 11 | 12 | FLYWAY_HOST= 13 | FLYWAY_PORT=3306 14 | FLYWAY_DB= 15 | FLYWAY_USER= 16 | FLYWAY_PASSWORD= 17 | 18 | while getopts e:H:P:d:u:p: OPT 19 | do 20 | case ${OPT} in 21 | "e") ENV_NAME="$OPTARG" ;; 22 | "H") FLYWAY_HOST="$OPTARG" ;; 23 | "P") FLYWAY_PORT="$OPTARG" ;; 24 | "d") FLYWAY_DB="$OPTARG" ;; 25 | "u") FLYWAY_USER="$OPTARG" ;; 26 | "p") FLYWAY_PASSWORD="$OPTARG" ;; 27 | esac 28 | done 29 | 30 | pushd ../../charts 31 | 32 | BASE_DIR=./thread-weaver-flyway 33 | 34 | if [[ "${ENV_NAME}" = "prod" ]]; then 35 | 36 | if [[ -z "${AWS_PROFILE}" ]]; then 37 | echo "plz set AWS_PROFILE" 38 | exit 2 39 | fi 40 | 41 | if [[ -z "${FLYWAY_HOST}" ]] || [[ -z "${FLYWAY_PORT}" ]] || \ 42 | [[ -z "${FLYWAY_DB}" ]] || [[ -z "${FLYWAY_USER}" ]] || [[ -z "${FLYWAY_PASSWORD}" ]]; then 43 | echo "plz set FLYWAY_* Parameters" 44 | exit 3; 45 | fi 46 | FLYWAY_HOST=${FLYWAY_HOST} \ 47 | FLYWAY_PORT=${FLYWAY_PORT} \ 48 | FLYWAY_DB=${FLYWAY_DB} \ 49 | FLYWAY_USER=${FLYWAY_USER} \ 50 | FLYWAY_PASSWORD=${FLYWAY_PASSWORD} \ 51 | ACCOUNT_ID=$(aws sts get-caller-identity --profile ${AWS_PROFILE} | jq -r '.Account') \ 52 | envsubst < ${BASE_DIR}/environments/${ENV_NAME}-values.yaml.tpl > ${BASE_DIR}/environments/${ENV_NAME}-values.yaml 53 | 54 | echo "generated ${BASE_DIR}/environments/${ENV_NAME}-values.yaml" 55 | 56 | fi 57 | 58 | helm install ${BASE_DIR} -f ${BASE_DIR}/environments/${ENV_NAME}-values.yaml --namespace thread-weaver 59 | 60 | popd 61 | -------------------------------------------------------------------------------- /tools/deploy/eks/.gitignore: -------------------------------------------------------------------------------- 1 | flyway.conf 2 | secrets.yaml 3 | dd-agent-values.yaml -------------------------------------------------------------------------------- /tools/deploy/eks/deploy-dd-agent.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | helm upgrade dd-agent --install -f dd-agent-values.yaml --namespace thread-weaver stable/datadog --recreate-pods 4 | -------------------------------------------------------------------------------- /tools/deploy/eks/push-image-to-ecr.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | 7 | pushd ../../../ 8 | AWS_DEFAULT_PROFILE=thread-weaver sbt api-server/ecr:push 9 | popd -------------------------------------------------------------------------------- /tools/deploy/eks/secrets.yaml.default: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: thread-weaver-app-secrets 5 | namespace: thread-weaver 6 | type: Opaque 7 | data: 8 | mysql.password: xxxxx(echo -n '' | base64) 9 | -------------------------------------------------------------------------------- /tools/deploy/eks/test-application.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | 7 | API_HOST=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.status.loadBalancer.ingress[0].hostname}") 8 | API_PORT=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.spec.ports[?(@.name==\"api\")].port}") 9 | 10 | echo "API_HOST=${API_HOST}" 11 | echo "API_PORT=${API_PORT}" 12 | 13 | if [[ -z "${API_HOST}" ]]; then 14 | echo "Failed to get api host" 15 | exit 1 16 | fi 17 | 18 | if [[ -z "${API_PORT}" ]]; then 19 | echo "Failed to get api port" 20 | exit 2 21 | fi 22 | 23 | ACCOUNT_ID=01DB5QXD4NP0XQTV92K42B3XBF 24 | ADMINISTRATOR_ID=01DB5QXD4NP0XQTV92K42B3XBF 25 | 26 | THREAD_ID=$(curl -v -X POST "http://$API_HOST:$API_PORT/v1/threads/create" -H "accept: application/json" -H "Content-Type: application/json" \ 27 | -d "{\"accountId\":\"${ACCOUNT_ID}\",\"title\":\"string\",\"remarks\":\"string\",\"administratorIds\":[\"${ADMINISTRATOR_ID}\"],\"memberIds\":[\"${ACCOUNT_ID}\"],\"createAt\":10000}" | jq -r .thread_id) 28 | echo "THREAD_ID=$THREAD_ID" 29 | 30 | if [[ -z "${THREAD_ID}" ]]; then 31 | echo "Failed to create thread" 32 | exit 3 33 | fi 34 | 35 | sleep 3 36 | curl -v -X GET "http://$API_HOST:$API_PORT/v1/threads/${THREAD_ID}?account_id=${ACCOUNT_ID}" -H "accept: application/json" 37 | -------------------------------------------------------------------------------- /tools/deploy/eks/test-management.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | API_HOST=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.status.loadBalancer.ingress[0].hostname}") 6 | MANAGEMENT_PORT=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.spec.ports[?(@.name==\"management\")].port}") 7 | 8 | echo "API_HOST=${API_HOST}" 9 | echo "MANAGEMENT_PORT=${MANAGEMENT_PORT}" 10 | 11 | if [[ -z "${MANAGEMENT_PORT}" ]]; then 12 | echo "Failed to get management port" 13 | exit 1 14 | fi 15 | 16 | curl -s -v http://${API_HOST}:${MANAGEMENT_PORT}/cluster/members | jq 17 | 18 | -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/build-image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | 7 | pushd ../../../ 8 | sbt api-server/docker:publishLocal 9 | popd -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/deploy-local-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | 7 | if [[ $# == 0 ]]; then 8 | echo "Parameters are empty." 9 | exit 1 10 | fi 11 | 12 | while getopts e: OPT 13 | do 14 | case ${OPT} in 15 | "e") ENV_NAME="$OPTARG" ;; 16 | esac 17 | done 18 | 19 | pushd ../../../charts 20 | 21 | helm install ./mysql --namespace thread-weaver -f ./mysql/environments/${ENV_NAME}-values.yaml --wait 22 | helm install ./dynamodb --namespace thread-weaver -f ./dynamodb/environments/${ENV_NAME}-values.yaml --wait 23 | 24 | popd 25 | -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/migrate-local-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | cd $(dirname $0) 6 | HOST=localhost 7 | pushd ../../../ 8 | 9 | sbt -Dmysql.host="$HOST" -Dmysql.port=30306 'migrate-mysql/run' 10 | DYNAMODB_HOST="$HOST" DYNAMODB_PORT=32000 sbt 'migrate-dynamodb/run' 11 | 12 | popd 13 | -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: thread-weaver-app-secrets 5 | namespace: thread-weaver 6 | type: Opaque 7 | data: 8 | mysql.password: cGFzc3dk 9 | -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/test-application.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | API_HOST=localhost 6 | API_PORT=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.spec.ports[?(@.name==\"api\")].nodePort}") 7 | 8 | echo "API_HOST=${API_HOST}" 9 | echo "API_PORT=${API_PORT}" 10 | 11 | if [[ -z "${API_PORT}" ]]; then 12 | echo "Failed to get port" 13 | exit 1 14 | fi 15 | 16 | ACCOUNT_ID=01DB5QXD4NP0XQTV92K42B3XBF 17 | ADMINISTRATOR_ID=01DB5QXD4NP0XQTV92K42B3XBF 18 | 19 | THREAD_ID=$(curl -v -X POST "http://$API_HOST:$API_PORT/v1/threads/create" -H "accept: application/json" -H "Content-Type: application/json" \ 20 | -d "{\"accountId\":\"${ACCOUNT_ID}\",\"title\":\"string\",\"remarks\":\"string\",\"administratorIds\":[\"${ADMINISTRATOR_ID}\"],\"memberIds\":[\"${ACCOUNT_ID}\"],\"createAt\":10000}" | jq -r .threadId) 21 | echo "THREAD_ID=$THREAD_ID" 22 | 23 | if [[ -z "${THREAD_ID}" ]]; then 24 | echo "Failed to create thread" 25 | exit 2 26 | fi 27 | 28 | sleep 3 29 | curl -v -X GET "http://$API_HOST:$API_PORT/v1/threads/${THREAD_ID}?account_id=${ACCOUNT_ID}" -H "accept: application/json" 30 | 31 | -------------------------------------------------------------------------------- /tools/deploy/k8s-d4m/test-management.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | API_HOST=localhost 6 | MANAGEMENT_PORT=$(kubectl get svc thread-weaver-api-server -n thread-weaver -ojsonpath="{.spec.ports[?(@.name==\"management\")].nodePort}") 7 | 8 | echo "API_HOST=${API_HOST}" 9 | echo "MANAGEMENT_PORT=${MANAGEMENT_PORT}" 10 | 11 | if [[ -z "${MANAGEMENT_PORT}" ]]; then 12 | echo "Failed to get management port" 13 | exit 1 14 | fi 15 | 16 | curl -s -v http://${API_HOST}:${MANAGEMENT_PORT}/cluster/members | jq 17 | 18 | -------------------------------------------------------------------------------- /tools/deploy/k8s-setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | kubectl apply -f rbac-config.yaml 6 | helm init --service-account tiller --wait 7 | kubectl create namespace thread-weaver 8 | kubectl create serviceaccount thread-weaver -n thread-weaver 9 | 10 | -------------------------------------------------------------------------------- /tools/deploy/rbac-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: tiller 5 | namespace: kube-system 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1beta1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: tiller 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: tiller 18 | namespace: kube-system 19 | -------------------------------------------------------------------------------- /tools/dynamodb/create-table.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd `dirname $0` 3 | 4 | AWS_ACCESS_KEY_ID=x AWS_SECRET_ACCESS_KEY=x aws dynamodb create-table \ 5 | --endpoint-url http://localhost:8000 \ 6 | --cli-input-json file://./table.json 7 | -------------------------------------------------------------------------------- /tools/dynamodb/table.json: -------------------------------------------------------------------------------- 1 | { 2 | "TableName": "Journal", 3 | "AttributeDefinitions": [ 4 | { 5 | "AttributeName": "pkey", 6 | "AttributeType": "S" 7 | }, 8 | { 9 | "AttributeName": "persistence-id", 10 | "AttributeType": "S" 11 | }, 12 | { 13 | "AttributeName": "sequence-nr", 14 | "AttributeType": "N" 15 | }, 16 | { 17 | "AttributeName": "tags", 18 | "AttributeType": "S" 19 | } 20 | ], 21 | "KeySchema": [ 22 | { 23 | "KeyType": "HASH", 24 | "AttributeName": "pkey" 25 | }, 26 | { 27 | "KeyType": "RANGE", 28 | "AttributeName": "sequence-nr" 29 | } 30 | ], 31 | "ProvisionedThroughput": { 32 | "WriteCapacityUnits": 10, 33 | "ReadCapacityUnits": 10 34 | } 35 | } -------------------------------------------------------------------------------- /tools/flyway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-alpine 2 | 3 | RUN apk --no-cache add --update bash openssl curl mysql-client 4 | 5 | # Add the flyway user and step in the directory 6 | RUN adduser -S -h /flyway -D flyway 7 | WORKDIR /flyway 8 | 9 | # Change to the flyway user 10 | USER flyway 11 | 12 | ENV FLYWAY_VERSION 5.2.0 13 | ENV MYSQL_DRIVER_VERSION 5.1.47 14 | 15 | RUN curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/${FLYWAY_VERSION}/flyway-commandline-${FLYWAY_VERSION}.tar.gz -o flyway-commandline-${FLYWAY_VERSION}.tar.gz \ 16 | && tar -xzf flyway-commandline-${FLYWAY_VERSION}.tar.gz --strip-components=1 \ 17 | && rm flyway-commandline-${FLYWAY_VERSION}.tar.gz 18 | 19 | RUN rm -f /flyway/drivers/mysql-*.jar 20 | 21 | ADD https://repo1.maven.org/maven2/mysql/mysql-connector-java/${MYSQL_DRIVER_VERSION}/mysql-connector-java-${MYSQL_DRIVER_VERSION}.jar /flyway/jars/ 22 | 23 | COPY ./src/test/resources/db-migration/* /flyway/sql 24 | 25 | ENTRYPOINT ["/flyway/flyway"] 26 | CMD ["-?"] -------------------------------------------------------------------------------- /tools/flyway/config.env: -------------------------------------------------------------------------------- 1 | # Port to run the container 2 | PORT=4000 3 | 4 | # Until here you can define all the individual configurations for your app -------------------------------------------------------------------------------- /tools/flyway/deploy.env: -------------------------------------------------------------------------------- 1 | APP_NAME=j5ik2o/thread-weaver-flyway 2 | AWS_CLI_PROFILE=thread-weaver 3 | AWS_CLI_REGION=ap-northeast-1 -------------------------------------------------------------------------------- /tools/flyway/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "1.0.0" 3 | 4 | -------------------------------------------------------------------------------- /tools/gatling-aggregate-runner/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | gatling { 2 | notice { 3 | slack { 4 | incoming-webhook-url = ${TW_GATLING_NOTICE_SLACK_INCOMING_WEBHOOK_URL} 5 | } 6 | } 7 | ecs-cluster-name = ${TW_GATLING_ECS_CLUSTER_NAME} 8 | task-definition = ${TW_GATLING_TASK_DEFINITION} 9 | count = ${TW_GATLING_COUNT} 10 | subnets = [${TW_GATLING_SUBNET}] 11 | assign-public-ip = "ENABLED" 12 | container-override-name = "gatling-runner" 13 | log-prefix = "thread-weaver/" 14 | environments = { 15 | "AWS_REGION": "ap-northeast-1", 16 | "TW_GATLING_TARGET_ENDPOINT_BASE_URL": ${TW_GATLING_TARGET_ENDPOINT_BASE_URL}, 17 | "TW_GATLING_PAUSE_DURATION": ${TW_GATLING_PAUSE_DURATION}, 18 | "TW_GATLING_HOLD_DURATION": ${TW_GATLING_HOLD_DURATION}, 19 | "TW_GATLING_RAMP_DURATION": ${TW_GATLING_RAMP_DURATION}, 20 | "TW_GATLING_RESULT_DIR": "target/gatling", 21 | "TW_GATLING_S3_BUCKET_NAME": ${TW_GATLING_BUCKET_NAME}, 22 | "TW_GATLING_SIMULATION_CLASS": ${TW_GATLING_SIMULATION_CLASS}, 23 | "TW_GATLING_USERS": ${TW_GATLING_USERS} 24 | } 25 | reporter { 26 | task-definition = ${TW_GATLING_REPORTER_TASK_DEFINITION} 27 | container-override-name = "gatling-s3-reporter" 28 | environments = { 29 | "AWS_REGION": "ap-northeast-1" 30 | "TW_GATLING_BUCKET_NAME" : ${TW_GATLING_BUCKET_NAME} 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /tools/gatling-aggregate-runner/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%thread] [%logger{36}] - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tools/gatling-runner/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | gatling-runner: 4 | image: j5ik2o/thread-weaver-gatling-runner:latest 5 | environment: 6 | - AWS_REGION=ap-northeast-1 7 | - AWS_PROFILE=thread-weaver 8 | - TW_GATLING_SIMULATION_CLASS=com.github.j5ik2o.gatling.ThreadSimulation 9 | - TW_GATLING_EXECUTION_ID=default 10 | - TW_GATLING_USERS=1 11 | - TW_GATLING_RAMP_DURATION=1m 12 | - TW_GATLING_HOLD_DURATION=2m 13 | - TW_GATLING_S3_BUCKET_NAME=thread-weaver-gatling-logs 14 | - TW_GATLING_RESULT_DIR=target/gatling 15 | - TW_GATLING_ENDPOINT_BASE_URL=http://10.0.1.7:18080/v1 16 | volumes: 17 | - ~/.aws/credentials:/root/.aws/credentials 18 | -------------------------------------------------------------------------------- /tools/gatling-runner/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | thread-weaver { 2 | gatling { 3 | simulation-classname = "com.github.j5ik2o.threadWeaver.gatling.CreateReactionSimulation" 4 | simulation-classname = ${?TW_GATLING_SIMULATION_CLASS} 5 | 6 | execution-id = "default" 7 | execution-id = ${?TW_GATLING_EXECUTION_ID} 8 | 9 | target-endpoint-base-url = "http://localhost:18080/v1" 10 | target-endpoint-base-url = ${?TW_GATLING_TARGET_ENDPOINT_BASE_URL} 11 | 12 | users = 10 13 | users = ${?TW_GATLING_USERS} 14 | 15 | ramp-duration = 10s 16 | ramp-duration = ${?TW_GATLING_RAMP_DURATION} 17 | 18 | pause-duration = 10s 19 | pause-duration = ${?TW_GATLING_PAUSE_DURATION} 20 | 21 | hold-duration = 30s 22 | hold-duration = ${?TW_GATLING_HOLD_DURATION} 23 | 24 | aws-s3-endpoint = "" 25 | aws-s3-endpoint = ${?TW_GATLING_S3_ENDPOINT} 26 | 27 | aws-s3-bucket-name = "thread-weaver-gatling-logs" 28 | 29 | aws-s3-bucket-name = ${?TW_GATLING_S3_BUCKET_NAME} 30 | aws-s3-source-file-name = ${?TW_GATLING_S3_SOURCE_FILE_NAME} 31 | 32 | aws-s3-create-bucket-on-start = false 33 | aws-s3-create-bucket-on-start = ${?TW_GATLING_S3_CREATE_BUCKET} 34 | 35 | aws-s3-path-style-access = false 36 | aws-s3-path-style-access = ${?TW_GATLING_S3_PATH_STYLE_ACCESS} 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /tools/gatling-runner/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%thread] [%logger{36}] - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | 3 | LABEL version=0.0.1 4 | 5 | ARG AWS_VERSION=1.16.58 6 | ARG GATLING_VERSION=3.1.2 7 | 8 | 9 | RUN apk --no-cache add py3-pip jq \ 10 | && pip3 install --no-cache-dir --upgrade pip \ 11 | && pip3 install --no-cache-dir awscli==${AWS_VERSION} 12 | 13 | WORKDIR /opt 14 | 15 | # install gatling 16 | RUN mkdir -p gatling && mkdir -p work && apk add --update wget bash libc6-compat 17 | 18 | RUN mkdir -p /tmp/downloads && \ 19 | wget -q -O /tmp/downloads/gatling-${GATLING_VERSION}.zip https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/${GATLING_VERSION}/gatling-charts-highcharts-bundle-${GATLING_VERSION}-bundle.zip 20 | 21 | RUN mkdir -p /tmp/archive && cd /tmp/archive && \ 22 | unzip /tmp/downloads/gatling-${GATLING_VERSION}.zip && \ 23 | mv /tmp/archive/gatling-charts-highcharts-bundle-${GATLING_VERSION}/* /opt/gatling/ && \ 24 | rm -rf /tmp/* 25 | 26 | # change context to gatling directory 27 | WORKDIR /opt/work 28 | 29 | ENV GATLING_HOME /opt/gatling 30 | 31 | COPY generate-report.sh /generate-report.sh 32 | 33 | ENTRYPOINT ["/generate-report.sh"] 34 | -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/config.env: -------------------------------------------------------------------------------- 1 | # Port to run the container 2 | PORT=4000 3 | 4 | # Until here you can define all the individual configurations for your app -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/deploy.env: -------------------------------------------------------------------------------- 1 | APP_NAME=j5ik2o/thread-weaver-gatling-s3-reporter 2 | AWS_CLI_PROFILE=thread-weaver 3 | AWS_CLI_REGION=ap-northeast-1 -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | gatling-s3-reporter: 4 | image: j5ik2o/gatling-s3-reporter:latest 5 | environment: 6 | - AWS_DEFAULT_REGION=ap-northeast-1 7 | - AWS_PROFILE=thread-weaver 8 | - S3_GATLING_BUCKET_NAME=thread-weaver-gatling-logs 9 | - S3_GATLING_RESULT_DIR_PATH=default 10 | volumes: 11 | - ~/.aws/credentials:/root/.aws/credentials 12 | -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/generate-report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | if [[ -z ${TW_GATLING_BUCKET_NAME} ]]; then 6 | echo "env TW_GATLING_BUCKET_NAME does not exist" 7 | exit 1 8 | fi 9 | 10 | if [[ -z ${TW_GATLING_RESULT_DIR_PATH} ]]; then 11 | echo "env TW_GATLING_RESULT_DIR_PATH does not exist" 12 | exit 1 13 | fi 14 | 15 | mkdir -p ${TW_GATLING_RESULT_DIR_PATH} 16 | 17 | # copy logs from s3 18 | /usr/bin/aws s3 cp s3://${TW_GATLING_BUCKET_NAME}/${TW_GATLING_RESULT_DIR_PATH}/ ${GATLING_HOME}/results/${TW_GATLING_RESULT_DIR_PATH} --recursive --exclude "*" --include "*.log" 19 | 20 | # create report 21 | /opt/gatling/bin/gatling.sh -ro ${TW_GATLING_RESULT_DIR_PATH} 22 | 23 | # copy report files to s3 (excluding logs) 24 | /usr/bin/aws s3 cp ${GATLING_HOME}/results/${TW_GATLING_RESULT_DIR_PATH} s3://${TW_GATLING_BUCKET_NAME}/${TW_GATLING_RESULT_DIR_PATH}/ --recursive --exclude "*.log" 25 | 26 | echo [report url] https://${TW_GATLING_BUCKET_NAME}.s3.amazonaws.com/${TW_GATLING_RESULT_DIR_PATH}/index.html 27 | -------------------------------------------------------------------------------- /tools/gatling-s3-reporter/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "1.0.0" 3 | 4 | -------------------------------------------------------------------------------- /tools/gatling-test/src/it/resources/gatling.conf: -------------------------------------------------------------------------------- 1 | gatling { 2 | core { 3 | directory { 4 | results = "target/gatling" # Name of the folder where all reports folder are located 5 | results = ${?TW_GATLING_RESULT_DIR} 6 | } 7 | } 8 | charting { 9 | noReports = false # When set to true, don't generate HTML reports 10 | indicators { 11 | lowerBound = 20 # Lower bound for the requests' response time to track in the reports and the console summary 12 | higherBound = 50 # Higher bound for the requests' response time to track in the reports and the console summary 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tools/gatling-test/src/it/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%level] [%date{yyyy-MM-dd'T'HH:mm:ss.SSSZ}] [%thread] [%logger{36}] - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tools/gatling-test/src/it/resources/reference.conf: -------------------------------------------------------------------------------- 1 | thread-weaver { 2 | gatling { 3 | target-endpoint-base-url = "http://localhost:18080/v1" 4 | target-endpoint-base-url = ${?TW_GATLING_TARGET_ENDPOINT_BASE_URL} 5 | 6 | users = 10 7 | users = ${?TW_GATLING_USERS} 8 | ramp-duration = 10s 9 | ramp-duration = ${?TW_GATLING_RAMP_DURATION} 10 | pause-duration = 10s 11 | pause-duration = ${?TW_GATLING_PAUSE_DURATION} 12 | hold-duration = 30s 13 | hold-duration = ${?TW_GATLING_HOLD_DURATION} 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /tools/local-dynamodb/native-libs/libsqlite4java-linux-amd64-1.0.392.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/tools/local-dynamodb/native-libs/libsqlite4java-linux-amd64-1.0.392.so -------------------------------------------------------------------------------- /tools/local-dynamodb/native-libs/libsqlite4java-linux-i386-1.0.392.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/tools/local-dynamodb/native-libs/libsqlite4java-linux-i386-1.0.392.so -------------------------------------------------------------------------------- /tools/local-dynamodb/native-libs/libsqlite4java-osx-1.0.392.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/tools/local-dynamodb/native-libs/libsqlite4java-osx-1.0.392.dylib -------------------------------------------------------------------------------- /tools/local-dynamodb/native-libs/sqlite4java-win32-x64-1.0.392.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/tools/local-dynamodb/native-libs/sqlite4java-win32-x64-1.0.392.dll -------------------------------------------------------------------------------- /tools/local-dynamodb/native-libs/sqlite4java-win32-x86-1.0.392.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j5ik2o/thread-weaver/39896c6e9ff05a279b3eb300965cd0e560c3150f/tools/local-dynamodb/native-libs/sqlite4java-win32-x86-1.0.392.dll -------------------------------------------------------------------------------- /tools/migrate-dynamodb/src/main/scala/com/github/j5ik2o/threadWeaver/dynmodb/Main.scala: -------------------------------------------------------------------------------- 1 | package com.github.j5ik2o.threadWeaver.dynmodb 2 | import java.net.URI 3 | 4 | import com.github.j5ik2o.reactive.aws.dynamodb.DynamoDbAsyncClient 5 | import org.slf4j.LoggerFactory 6 | import software.amazon.awssdk.auth.credentials.{ AwsBasicCredentials, StaticCredentialsProvider } 7 | import software.amazon.awssdk.services.dynamodb.{ DynamoDbAsyncClient => JavaDynamoDbAsyncClient } 8 | 9 | import scala.util.control.NonFatal 10 | 11 | object Main extends App with DynamoDBCreator { 12 | val logger = LoggerFactory.getLogger(getClass) 13 | 14 | val awsAccessKeyId: String = sys.env.getOrElse("DYNAMODB_AWS_ACCESS_KEY_ID", "x") 15 | val awsSecretAccessKey: String = sys.env.getOrElse("DYNAMODB_AWS_SECRET_ACCESS_KEY", "x") 16 | 17 | val dynamoDBHost: String = sys.env.getOrElse("DYNAMODB_HOST", "localhost") 18 | val dynamoDBPort: Int = sys.env.getOrElse("DYNAMODB_PORT", "8000").toInt 19 | 20 | val dynamoDBEndpoint: String = s"http://$dynamoDBHost:$dynamoDBPort" 21 | 22 | lazy val javaClient: JavaDynamoDbAsyncClient = JavaDynamoDbAsyncClient 23 | .builder() 24 | .credentialsProvider( 25 | StaticCredentialsProvider.create(AwsBasicCredentials.create(awsAccessKeyId, awsSecretAccessKey)) 26 | ) 27 | .endpointOverride(URI.create(dynamoDBEndpoint)) 28 | .build() 29 | 30 | lazy val scalaClient: DynamoDbAsyncClient = DynamoDbAsyncClient(javaClient) 31 | 32 | try { 33 | createJournalTable() 34 | createSnapshotTable() 35 | } catch { 36 | case NonFatal(ex) => 37 | logger.error("occurred create table error", ex) 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /tools/terraform/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | config/ 3 | eks-terraform-env.sh -------------------------------------------------------------------------------- /tools/terraform/dynamodb/output.tf: -------------------------------------------------------------------------------- 1 | output "aws_dynamodb_table_journal_table_name" { 2 | value = "${aws_dynamodb_table.journal-table.0.name}" 3 | } 4 | 5 | output "aws_dynamodb_table_snapshot_table_name" { 6 | value = "${aws_dynamodb_table.snapshot-table.0.name}" 7 | } -------------------------------------------------------------------------------- /tools/terraform/dynamodb/variables.tf: -------------------------------------------------------------------------------- 1 | variable "enabled" { 2 | default = true 3 | } 4 | 5 | variable "prefix" {} 6 | 7 | variable "owner" {} 8 | 9 | variable "journal_table_name" {} 10 | 11 | variable "snapshot_table_name" {} -------------------------------------------------------------------------------- /tools/terraform/ecr/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ecr_repository" "this" { 2 | count = "${var.enabled ? 1 : 0}" 3 | name = "${var.ecr_name}" 4 | lifecycle { 5 | create_before_destroy = true 6 | } 7 | tags = { 8 | Owner = "${var.owner}" 9 | } 10 | } 11 | 12 | resource "aws_ecr_repository_policy" "this" { 13 | count = "${var.enabled ? 1 : 0}" 14 | policy = <