├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── micro-app ├── .gitignore ├── README.md ├── build.gradle ├── libs │ └── .gitkeep └── src │ ├── main │ ├── java │ │ └── sample │ │ │ ├── MicroApp.java │ │ │ ├── MicroAppConfig.java │ │ │ ├── api │ │ │ ├── MasterFacadeExporter.java │ │ │ ├── admin │ │ │ │ ├── MasterAdminFacadeExporter.java │ │ │ │ ├── SystemAdminFacadeExporter.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── usecase │ │ │ ├── AccountService.java │ │ │ ├── MasterAdminService.java │ │ │ ├── SystemAdminService.java │ │ │ └── package-info.java │ └── resources │ │ ├── application-app.yml │ │ └── banner.txt │ └── test │ ├── java │ └── sample │ │ ├── EntityTestSupport.java │ │ └── support │ │ └── MockDomainHelper.java │ └── resources │ ├── application-test.yml │ ├── application-testweb.yml │ └── logback.xml ├── micro-asset ├── .gitignore ├── README.md ├── build.gradle ├── libs │ └── .gitkeep └── src │ ├── main │ ├── java │ │ └── sample │ │ │ └── microasset │ │ │ ├── MicroAsset.java │ │ │ ├── MicroAssetConfig.java │ │ │ ├── MicroAssetDbConfig.java │ │ │ ├── api │ │ │ ├── AssetFacade.java │ │ │ ├── AssetFacadeExporter.java │ │ │ ├── admin │ │ │ │ ├── AssetAdminFacade.java │ │ │ │ ├── AssetAdminFacadeExporter.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ ├── context │ │ │ └── orm │ │ │ │ └── AssetRepository.java │ │ │ ├── model │ │ │ ├── AssetDataFixtures.java │ │ │ └── asset │ │ │ │ ├── Asset.java │ │ │ │ ├── AssetErrorKeys.java │ │ │ │ ├── CashBalance.java │ │ │ │ ├── CashInOut.java │ │ │ │ ├── Cashflow.java │ │ │ │ ├── Remarks.java │ │ │ │ ├── package-info.java │ │ │ │ └── type │ │ │ │ ├── CashflowType.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── usecase │ │ │ ├── AssetAdminService.java │ │ │ ├── AssetService.java │ │ │ ├── event │ │ │ └── AppMailEvent.java │ │ │ ├── mail │ │ │ ├── ServiceMailDeliver.java │ │ │ └── package-info.java │ │ │ └── package-info.java │ └── resources │ │ ├── application-asset.yml │ │ └── banner.txt │ └── test │ ├── java │ └── sample │ │ ├── EntityTestSupport.java │ │ ├── microasset │ │ └── model │ │ │ └── asset │ │ │ ├── AssetTest.java │ │ │ ├── CashBalanceTest.java │ │ │ ├── CashInOutTest.java │ │ │ └── CashflowTest.java │ │ └── support │ │ └── MockDomainHelper.java │ └── resources │ ├── application-test.yml │ ├── application-testweb.yml │ └── logback.xml ├── micro-core ├── .gitignore ├── README.md ├── build.gradle ├── libs │ └── .gitkeep └── src │ ├── main │ ├── java │ │ └── sample │ │ │ ├── ActionStatusType.java │ │ │ ├── ApplicationConfig.java │ │ │ ├── ApplicationDbConfig.java │ │ │ ├── ApplicationSecurityConfig.java │ │ │ ├── InvocationException.java │ │ │ ├── ValidationException.java │ │ │ ├── api │ │ │ ├── ApiClient.java │ │ │ ├── ApiUtils.java │ │ │ ├── admin │ │ │ │ ├── MasterAdminFacade.java │ │ │ │ ├── SystemAdminFacade.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ ├── context │ │ │ ├── AppSetting.java │ │ │ ├── AppSettingHandler.java │ │ │ ├── DomainHelper.java │ │ │ ├── Dto.java │ │ │ ├── Entity.java │ │ │ ├── Repository.java │ │ │ ├── ResourceBundleHandler.java │ │ │ ├── SimpleObjectProvider.java │ │ │ ├── Timestamper.java │ │ │ ├── actor │ │ │ │ ├── Actor.java │ │ │ │ ├── ActorSession.java │ │ │ │ └── package-info.java │ │ │ ├── audit │ │ │ │ ├── AuditActor.java │ │ │ │ ├── AuditEvent.java │ │ │ │ ├── AuditHandler.java │ │ │ │ └── package-info.java │ │ │ ├── lock │ │ │ │ ├── IdLockHandler.java │ │ │ │ └── package-info.java │ │ │ ├── mail │ │ │ │ ├── MailHandler.java │ │ │ │ └── package-info.java │ │ │ ├── orm │ │ │ │ ├── DefaultRepository.java │ │ │ │ ├── OrmActiveMetaRecord.java │ │ │ │ ├── OrmActiveRecord.java │ │ │ │ ├── OrmCriteria.java │ │ │ │ ├── OrmDataSourceProperties.java │ │ │ │ ├── OrmInterceptor.java │ │ │ │ ├── OrmQueryMetadata.java │ │ │ │ ├── OrmRepository.java │ │ │ │ ├── OrmTemplate.java │ │ │ │ ├── OrmUtils.java │ │ │ │ ├── Pagination.java │ │ │ │ ├── PagingList.java │ │ │ │ ├── Sort.java │ │ │ │ ├── SystemRepository.java │ │ │ │ ├── TxTemplate.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── report │ │ │ │ ├── ReportFile.java │ │ │ │ ├── ReportHandler.java │ │ │ │ └── package-info.java │ │ │ ├── rest │ │ │ │ ├── RestActorSessionBindFilter.java │ │ │ │ ├── RestActorSessionInterceptor.java │ │ │ │ ├── RestInvoker.java │ │ │ │ └── package-info.java │ │ │ └── security │ │ │ │ ├── SecurityActorFinder.java │ │ │ │ ├── SecurityConfigurer.java │ │ │ │ ├── SecurityProperties.java │ │ │ │ └── package-info.java │ │ │ ├── controller │ │ │ ├── ControllerUtils.java │ │ │ ├── RestErrorAdvice.java │ │ │ ├── RestErrorController.java │ │ │ └── package-info.java │ │ │ ├── model │ │ │ ├── BusinessDayHandler.java │ │ │ ├── DataFixtures.java │ │ │ ├── DomainErrorKeys.java │ │ │ ├── account │ │ │ │ ├── Account.java │ │ │ │ ├── FiAccount.java │ │ │ │ ├── Login.java │ │ │ │ ├── package-info.java │ │ │ │ └── type │ │ │ │ │ ├── AccountStatusType.java │ │ │ │ │ └── package-info.java │ │ │ ├── constraints │ │ │ │ ├── AbsAmount.java │ │ │ │ ├── AbsAmountEmpty.java │ │ │ │ ├── Amount.java │ │ │ │ ├── AmountEmpty.java │ │ │ │ ├── Category.java │ │ │ │ ├── CategoryEmpty.java │ │ │ │ ├── Currency.java │ │ │ │ ├── CurrencyEmpty.java │ │ │ │ ├── Description.java │ │ │ │ ├── DescriptionEmpty.java │ │ │ │ ├── Email.java │ │ │ │ ├── EmailEmpty.java │ │ │ │ ├── ISODate.java │ │ │ │ ├── ISODateEmpty.java │ │ │ │ ├── ISODateTime.java │ │ │ │ ├── ISODateTimeEmpty.java │ │ │ │ ├── IdStr.java │ │ │ │ ├── IdStrEmpty.java │ │ │ │ ├── Name.java │ │ │ │ ├── NameEmpty.java │ │ │ │ ├── Outline.java │ │ │ │ ├── OutlineEmpty.java │ │ │ │ ├── Password.java │ │ │ │ ├── Year.java │ │ │ │ ├── YearEmpty.java │ │ │ │ └── package-info.java │ │ │ ├── master │ │ │ │ ├── Holiday.java │ │ │ │ ├── SelfFiAccount.java │ │ │ │ ├── Staff.java │ │ │ │ ├── StaffAuthority.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ ├── usecase │ │ │ ├── SecurityService.java │ │ │ ├── ServiceUtils.java │ │ │ └── package-info.java │ │ │ └── util │ │ │ ├── Calculator.java │ │ │ ├── Checker.java │ │ │ ├── ConvertUtils.java │ │ │ ├── DateUtils.java │ │ │ ├── Regex.java │ │ │ ├── TimePoint.java │ │ │ ├── Validator.java │ │ │ └── package-info.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── ehcache.xml │ │ ├── logback-spring.xml │ │ ├── messages-validation.properties │ │ └── messages.properties │ └── test │ ├── java │ └── sample │ │ ├── DdlExporter.java │ │ ├── EntityTestSupport.java │ │ ├── context │ │ └── ResourceBundleHandlerTest.java │ │ ├── model │ │ ├── account │ │ │ ├── AccountTest.java │ │ │ ├── FiAccountTest.java │ │ │ └── LoginTest.java │ │ └── master │ │ │ ├── HolidayTest.java │ │ │ ├── SelfFiAccountTest.java │ │ │ ├── StaffAuthorityTest.java │ │ │ └── StaffTest.java │ │ ├── support │ │ └── MockDomainHelper.java │ │ └── util │ │ ├── CalculatorTest.java │ │ ├── CheckerTest.java │ │ ├── ConvertUtilsTest.java │ │ ├── DateUtilsTest.java │ │ ├── TimePointTest.java │ │ └── ValidatorTest.java │ └── resources │ ├── application-test.yml │ └── logback.xml ├── micro-registry ├── .gitignore ├── README.md ├── build.gradle ├── libs │ └── .gitkeep └── src │ ├── main │ ├── java │ │ └── sample │ │ │ ├── MicroRegistry.java │ │ │ └── package-info.java │ └── resources │ │ ├── application.yml │ │ └── banner.txt │ └── test │ ├── java │ └── sample │ │ └── .gitkeep │ └── resources │ └── .gitkeep ├── micro-web ├── .gitignore ├── README.md ├── build.gradle ├── libs │ └── .gitkeep └── src │ ├── main │ ├── java │ │ └── sample │ │ │ ├── MicroWeb.java │ │ │ ├── MicroWebConfig.java │ │ │ ├── api │ │ │ ├── AssetFacadeInvoker.java │ │ │ ├── admin │ │ │ │ ├── AssetAdminFacadeInvoker.java │ │ │ │ ├── MasterAdminFacadeInvoker.java │ │ │ │ ├── SystemAdminFacadeInvoker.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ │ ├── controller │ │ │ ├── AccountController.java │ │ │ ├── AssetController.java │ │ │ ├── LoginInterceptor.java │ │ │ ├── admin │ │ │ │ ├── AssetAdminController.java │ │ │ │ ├── MasterAdminController.java │ │ │ │ ├── SystemAdminController.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── system │ │ │ │ ├── JobController.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ └── resources │ │ ├── application-web.yml │ │ └── banner.txt │ └── test │ ├── java │ └── sample │ │ └── client │ │ └── SampleClient.java │ └── resources │ ├── application-testweb.yml │ └── logback.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 jkazama 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /micro-app/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /micro-app/README.md: -------------------------------------------------------------------------------- 1 | micro-app 2 | --- 3 | 4 | マイクロサービスのドメインAPIプロセスです。 5 | 6 | - `api` パッケージ直下で内部プロセス向けの API を公開しています。 7 | - ドメインパッケージ ( account / asset etc) 単位で別プロセスに切り出し可能です。 8 | - 切り出す場合は `core` プロジェクトにある model / api パッケージの関連ドメイン移動も考えます。 9 | - 上記対応時は `web` プロジェクトから切り出したドメインプロジェクトへ参照を加える必要があります。 10 | -------------------------------------------------------------------------------- /micro-app/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | mainClassName = 'sample.MicroApp' 3 | classifier = 'exec' 4 | } 5 | 6 | dependencies { 7 | implementation project(':micro-core') 8 | } 9 | -------------------------------------------------------------------------------- /micro-app/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-app/libs/.gitkeep -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/MicroApp.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.context.annotation.Import; 8 | 9 | import sample.api.MasterFacadeExporter; 10 | import sample.usecase.AccountService; 11 | 12 | /** 13 | * アプリケーションプロセスの起動クラス。 14 | *

本クラスを実行する事でSpringBootが提供する組込Tomcatでのアプリケーション起動が行われます。 15 | *

自動的に Eureka Server へ登録されます。 16 | *

自動設定対象として以下のパッケージをスキャンしています。 17 | *

21 | */ 22 | @SpringBootApplication(scanBasePackageClasses = { 23 | AccountService.class, MasterFacadeExporter.class }) 24 | @Import(MicroAppConfig.class) 25 | @EnableCaching(proxyTargetClass = true) 26 | @EnableDiscoveryClient 27 | public class MicroApp { 28 | 29 | public static void main(String[] args) { 30 | new SpringApplicationBuilder(MicroApp.class) 31 | .profiles("app") 32 | .run(args); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/api/MasterFacadeExporter.java: -------------------------------------------------------------------------------- 1 | package sample.api; 2 | 3 | /** 4 | * マスタ系ユースケースの外部公開処理を表現します。 5 | *

low: 自動スキャン用に暫定作成 6 | */ 7 | public class MasterFacadeExporter { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/api/admin/MasterAdminFacadeExporter.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import static sample.api.admin.MasterAdminFacade.Path; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import sample.api.ApiUtils; 13 | import sample.model.master.*; 14 | import sample.model.master.Holiday.RegHoliday; 15 | import sample.usecase.MasterAdminService; 16 | 17 | /** 18 | * マスタ系社内ユースケースの外部公開処理を表現します。 19 | */ 20 | @RestController 21 | @RequestMapping(Path) 22 | public class MasterAdminFacadeExporter implements MasterAdminFacade { 23 | 24 | private final MasterAdminService service; 25 | 26 | public MasterAdminFacadeExporter(MasterAdminService service) { 27 | this.service = service; 28 | } 29 | 30 | /** {@inheritDoc} */ 31 | @Override 32 | @GetMapping(PathGetStaff) 33 | public Staff getStaff(@PathVariable String staffId) { 34 | return service.getStaff(staffId).orElse(null); 35 | } 36 | 37 | /** {@inheritDoc} */ 38 | @Override 39 | @GetMapping(PathFindStaffAuthority) 40 | public List findStaffAuthority(@PathVariable String staffId) { 41 | return service.findStaffAuthority(staffId); 42 | } 43 | 44 | /** {@inheritDoc} */ 45 | @Override 46 | @PostMapping(PathRegisterHoliday) 47 | public ResponseEntity registerHoliday(@RequestBody @Valid RegHoliday p) { 48 | return ApiUtils.resultEmpty(() -> service.registerHoliday(p)); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/api/admin/SystemAdminFacadeExporter.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import static sample.api.admin.SystemAdminFacade.Path; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import sample.api.ApiUtils; 13 | import sample.context.AppSetting; 14 | import sample.context.AppSetting.FindAppSetting; 15 | import sample.context.audit.*; 16 | import sample.context.audit.AuditActor.FindAuditActor; 17 | import sample.context.audit.AuditEvent.FindAuditEvent; 18 | import sample.context.orm.PagingList; 19 | import sample.usecase.SystemAdminService; 20 | 21 | /** 22 | * システム系社内ユースケースの外部公開処理を表現します。 23 | */ 24 | @RestController 25 | @RequestMapping(Path) 26 | public class SystemAdminFacadeExporter implements SystemAdminFacade { 27 | 28 | private final SystemAdminService service; 29 | 30 | public SystemAdminFacadeExporter(SystemAdminService service) { 31 | this.service = service; 32 | } 33 | 34 | /** {@inheritDoc} */ 35 | @Override 36 | @GetMapping(PathFindAudiActor) 37 | public PagingList findAuditActor(@Valid FindAuditActor p) { 38 | return service.findAuditActor(p); 39 | } 40 | 41 | /** {@inheritDoc} */ 42 | @Override 43 | @GetMapping(PathFindAudiEvent) 44 | public PagingList findAuditEvent(@Valid FindAuditEvent p) { 45 | return service.findAuditEvent(p); 46 | } 47 | 48 | /** {@inheritDoc} */ 49 | @Override 50 | @GetMapping(PathFindAppSetting) 51 | public List findAppSetting(@Valid FindAppSetting p) { 52 | return service.findAppSetting(p); 53 | } 54 | 55 | /** {@inheritDoc} */ 56 | @Override 57 | @PostMapping(PathChangeAppSetting) 58 | public ResponseEntity changeAppSetting(String id, String value) { 59 | return ApiUtils.resultEmpty(() -> service.changeAppSetting(id, value)); 60 | } 61 | 62 | /** {@inheritDoc} */ 63 | @Override 64 | @PostMapping(PathProcessDay) 65 | public ResponseEntity processDay() { 66 | return ApiUtils.resultEmpty(() -> service.processDay()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/api/admin/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する社内 API コンポーネント。 3 | */ 4 | package sample.api.admin; -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する API コンポーネント。 3 | */ 4 | package sample.api; -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーションルートディレクトリ。 3 | *

Applicationクラスを実行する事でアプリケーションプロセスが立ち上がります。 4 | */ 5 | package sample; -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/usecase/AccountService.java: -------------------------------------------------------------------------------- 1 | package sample.usecase; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.PlatformTransactionManager; 9 | 10 | import sample.context.orm.*; 11 | import sample.model.account.*; 12 | 13 | /** 14 | * 口座ドメインに対する顧客ユースケース処理。 15 | */ 16 | @Service 17 | public class AccountService { 18 | private final DefaultRepository rep; 19 | private final PlatformTransactionManager txm; 20 | 21 | public AccountService( 22 | DefaultRepository rep, 23 | @Qualifier(DefaultRepository.BeanNameTx) PlatformTransactionManager txm) { 24 | this.rep = rep; 25 | this.txm = txm; 26 | } 27 | 28 | /** ログイン情報を取得します。 */ 29 | @Cacheable("AccountService.getLoginByLoginId") 30 | public Optional getLoginByLoginId(String loginId) { 31 | return TxTemplate.of(txm).readOnly().tx( 32 | () -> Login.getByLoginId(rep, loginId)); 33 | } 34 | 35 | /** 有効な口座情報を取得します。 */ 36 | @Cacheable("AccountService.getAccount") 37 | public Optional getAccount(String id) { 38 | return TxTemplate.of(txm).readOnly().tx( 39 | () -> Account.getValid(rep, id)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/usecase/MasterAdminService.java: -------------------------------------------------------------------------------- 1 | package sample.usecase; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.cache.annotation.Cacheable; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.PlatformTransactionManager; 9 | 10 | import sample.context.audit.AuditHandler; 11 | import sample.context.orm.*; 12 | import sample.model.master.*; 13 | import sample.model.master.Holiday.RegHoliday; 14 | 15 | /** 16 | * サービスマスタドメインに対する社内ユースケース処理。 17 | */ 18 | @Service 19 | public class MasterAdminService { 20 | 21 | private final DefaultRepository rep; 22 | private final PlatformTransactionManager txm; 23 | private final AuditHandler audit; 24 | 25 | public MasterAdminService( 26 | DefaultRepository rep, 27 | @Qualifier(DefaultRepository.BeanNameTx) 28 | PlatformTransactionManager txm, 29 | AuditHandler audit) { 30 | this.rep = rep; 31 | this.txm = txm; 32 | this.audit = audit; 33 | } 34 | 35 | 36 | /** 社員を取得します。 */ 37 | @Cacheable("MasterAdminService.getStaff") 38 | public Optional getStaff(String id) { 39 | return TxTemplate.of(txm).readOnly().tx(() -> Staff.get(rep, id)); 40 | } 41 | 42 | /** 社員権限を取得します。 */ 43 | @Cacheable("MasterAdminService.findStaffAuthority") 44 | public List findStaffAuthority(String staffId) { 45 | return TxTemplate.of(txm).readOnly().tx(() -> StaffAuthority.find(rep, staffId)); 46 | } 47 | 48 | public void registerHoliday(final RegHoliday p) { 49 | audit.audit("休日情報を登録する", () -> { 50 | TxTemplate.of(txm).tx(() -> Holiday.register(rep, p)); 51 | }); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/usecase/SystemAdminService.java: -------------------------------------------------------------------------------- 1 | package sample.usecase; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Qualifier; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.PlatformTransactionManager; 8 | 9 | import sample.context.AppSetting; 10 | import sample.context.AppSetting.FindAppSetting; 11 | import sample.context.audit.*; 12 | import sample.context.audit.AuditActor.FindAuditActor; 13 | import sample.context.audit.AuditEvent.FindAuditEvent; 14 | import sample.context.orm.*; 15 | import sample.model.BusinessDayHandler; 16 | 17 | /** 18 | * システムドメインに対する社内ユースケース処理。 19 | */ 20 | @Service 21 | public class SystemAdminService { 22 | 23 | private final SystemRepository rep; 24 | private final PlatformTransactionManager txm; 25 | private final AuditHandler audit; 26 | private final BusinessDayHandler businessDay; 27 | 28 | public SystemAdminService( 29 | SystemRepository rep, 30 | @Qualifier(SystemRepository.BeanNameTx) 31 | PlatformTransactionManager txm, 32 | AuditHandler audit, 33 | BusinessDayHandler businessDay) { 34 | this.rep = rep; 35 | this.txm = txm; 36 | this.audit = audit; 37 | this.businessDay = businessDay; 38 | } 39 | 40 | /** 利用者監査ログを検索します。 */ 41 | public PagingList findAuditActor(FindAuditActor p) { 42 | return TxTemplate.of(txm).readOnly().tx(() -> AuditActor.find(rep, p)); 43 | } 44 | 45 | /** イベント監査ログを検索します。 */ 46 | public PagingList findAuditEvent(FindAuditEvent p) { 47 | return TxTemplate.of(txm).readOnly().tx(() -> AuditEvent.find(rep, p)); 48 | } 49 | 50 | /** アプリケーション設定一覧を検索します。 */ 51 | public List findAppSetting(FindAppSetting p) { 52 | return TxTemplate.of(txm).readOnly().tx(() -> AppSetting.find(rep, p)); 53 | } 54 | 55 | public void changeAppSetting(String id, String value) { 56 | audit.audit("アプリケーション設定情報を変更する", () -> rep.dh().settingSet(id, value)); 57 | } 58 | 59 | public void processDay() { 60 | audit.audit("営業日を進める", () -> rep.dh().time().proceedDay(businessDay.day(1))); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /micro-app/src/main/java/sample/usecase/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーション層のコンポーネント。 3 | *

UI層から呼び出され、ドメイン層のコンポーネントを利用してユースケース処理を実現します。 4 | * アプリケーション層のコンポーネントがUI層を呼び出す事やService同士で相互依存することは想定していません。 5 | */ 6 | package sample.usecase; -------------------------------------------------------------------------------- /micro-app/src/main/resources/application-app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: app 4 | application.name: micro-app 5 | 6 | server: 7 | port: 8090 8 | 9 | extension: 10 | datasource: 11 | default.jpa.hibernate.ddl-auto: create-drop 12 | system.jpa.hibernate.ddl-auto: create-drop 13 | datafixture.enabled: true 14 | 15 | --- 16 | spring: 17 | profiles: production 18 | 19 | extension: 20 | datasource: 21 | default.jpa.hibernate.ddl-auto: none 22 | system.jpa.hibernate.ddl-auto: none 23 | datafixture.enabled: false 24 | -------------------------------------------------------------------------------- /micro-app/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ====================================================================================== 2 | Start Application [ micro-app ] 3 | -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /micro-app/src/test/java/sample/support/MockDomainHelper.java: -------------------------------------------------------------------------------- 1 | package sample.support; 2 | 3 | import java.time.Clock; 4 | import java.util.*; 5 | 6 | import sample.context.*; 7 | import sample.context.actor.ActorSession; 8 | 9 | /** モックテスト用のドメインヘルパー */ 10 | public class MockDomainHelper extends DomainHelper { 11 | 12 | private Map settingMap = new HashMap<>(); 13 | 14 | public MockDomainHelper() { 15 | this(Clock.systemDefaultZone()); 16 | } 17 | 18 | public MockDomainHelper(final Clock mockClock) { 19 | setActorSession(new ActorSession()); 20 | setTime(new Timestamper(mockClock)); 21 | setSettingHandler(new AppSettingHandler(settingMap)); 22 | } 23 | 24 | public MockDomainHelper setting(String id, String value) { 25 | settingMap.put(id, value); 26 | return this; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /micro-app/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: test 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | eureka.client.enabled: false 8 | 9 | extension: 10 | datasource: 11 | default: 12 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 13 | username: 14 | password: 15 | jpa: 16 | show-sql: true 17 | hibernate.ddl-auto: create-drop 18 | system: 19 | url: jdbc:h2:mem:system;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 20 | username: 21 | password: 22 | jpa: 23 | show-sql: true 24 | hibernate.ddl-auto: create-drop 25 | security.auth.enabled: false 26 | datafixture.enabled: true 27 | -------------------------------------------------------------------------------- /micro-app/src/test/resources/application-testweb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: testweb 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | eureka.client.enabled: false 8 | 9 | extension: 10 | datasource: 11 | default: 12 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 13 | username: 14 | password: 15 | jpa.hibernate.ddl-auto: none 16 | system: 17 | url: jdbc:h2:mem:system;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 18 | username: 19 | password: 20 | jpa.hibernate.ddl-auto: none 21 | security.auth.enabled: false 22 | -------------------------------------------------------------------------------- /micro-app/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /micro-asset/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /micro-asset/README.md: -------------------------------------------------------------------------------- 1 | micro-asset 2 | --- 3 | 4 | マイクロサービスのドメインAPIプロセス ( 資産固有 ) です。 5 | 6 | - `api` パッケージ直下で内部プロセス向けの API を公開しています。 7 | - 資産に特化したサービスを提供します。 8 | - `core` プロジェクトに存在するドメイン情報については参照前提で利用します。変更が必要であれば `app` プロセスのAPIを呼び出して委譲します。 9 | - RemoteInvocation とは異なるため、web側の依存をなくして同 I/F なクラスで代替えする事も可能です。 10 | - 資産スキーマを AssetRepository によって標準スキーマとは別に管理しています。 11 | - マスタ系の標準スキーマへの依存も外したい場合はそこを API 経由にする方向で。(これ以上はキリがないので割愛) 12 | -------------------------------------------------------------------------------- /micro-asset/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | mainClassName = 'sample.microasset.MicroAsset' 3 | classifier = 'exec' 4 | } 5 | jar.enabled=true 6 | 7 | dependencies { 8 | implementation project(':micro-core') 9 | } 10 | -------------------------------------------------------------------------------- /micro-asset/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-asset/libs/.gitkeep -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/MicroAsset.java: -------------------------------------------------------------------------------- 1 | package sample.microasset; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.context.annotation.Import; 8 | 9 | import sample.microasset.api.AssetFacadeExporter; 10 | import sample.microasset.usecase.AssetService; 11 | 12 | /** 13 | * 資産アプリケーションプロセスの起動クラス。 14 | *

本クラスを実行する事でSpringBootが提供する組込Tomcatでのアプリケーション起動が行われます。 15 | *

自動的に Eureka Server へ登録されます。 16 | *

自動設定対象として以下のパッケージをスキャンしています。 17 | *

21 | */ 22 | @SpringBootApplication(scanBasePackageClasses = { AssetService.class, AssetFacadeExporter.class }) 23 | @Import(MicroAssetConfig.class) 24 | @EnableCaching(proxyTargetClass = true) 25 | @EnableDiscoveryClient 26 | public class MicroAsset { 27 | 28 | public static void main(String[] args) { 29 | new SpringApplicationBuilder(MicroAsset.class) 30 | .profiles("asset") 31 | .run(args); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/MicroAssetDbConfig.java: -------------------------------------------------------------------------------- 1 | package sample.microasset; 2 | 3 | import static sample.microasset.context.orm.AssetRepository.*; 4 | 5 | import javax.persistence.EntityManagerFactory; 6 | import javax.sql.DataSource; 7 | 8 | import org.springframework.beans.factory.annotation.Qualifier; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.context.annotation.*; 11 | import org.springframework.orm.jpa.*; 12 | 13 | import sample.microasset.context.orm.AssetRepository; 14 | import sample.microasset.context.orm.AssetRepository.AssetDataSourceProperties; 15 | 16 | /** 17 | * 資産ドメインのデータベース接続定義を表現します。 18 | */ 19 | @Configuration 20 | @EnableConfigurationProperties({AssetDataSourceProperties.class}) 21 | public class MicroAssetDbConfig { 22 | 23 | @Bean 24 | @DependsOn(BeanNameEmf) 25 | AssetRepository assetRepository() { 26 | return new AssetRepository(); 27 | } 28 | 29 | @Bean(name = BeanNameDs, destroyMethod = "close") 30 | DataSource assetDataSource(AssetDataSourceProperties props) { 31 | return props.dataSource(); 32 | } 33 | 34 | @Bean(name = BeanNameEmf) 35 | LocalContainerEntityManagerFactoryBean assetEntityManagerFactoryBean( 36 | AssetDataSourceProperties props, 37 | @Qualifier(AssetRepository.BeanNameDs) final DataSource dataSource) { 38 | return props.entityManagerFactoryBean(dataSource); 39 | } 40 | 41 | @Bean(name = BeanNameTx) 42 | JpaTransactionManager assetTransactionManager( 43 | AssetDataSourceProperties props, 44 | @Qualifier(AssetRepository.BeanNameEmf) final EntityManagerFactory emf) { 45 | return props.transactionManager(emf); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/AssetFacade.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.api; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | 7 | import sample.microasset.model.asset.CashInOut; 8 | import sample.microasset.model.asset.CashInOut.RegCashOut; 9 | 10 | /** 11 | * 資産関連のユースケース API を表現します。 12 | */ 13 | public interface AssetFacade { 14 | 15 | String Path = "/api/asset"; 16 | 17 | String PathFindUnprocessedCashOut = "/cio/unprocessedOut/"; 18 | String PathWithdraw = "/cio/withdraw"; 19 | 20 | /** 21 | * 未処理の振込依頼情報を検索します。 22 | * low: CashInOutは情報過多ですがアプリケーション層では公開対象を特定しにくい事もあり、 23 | * UI層に最終判断を委ねています。 24 | */ 25 | List findUnprocessedCashOut(); 26 | 27 | /** 28 | * 振込出金依頼をします。 29 | * low: 公開リスクがあるためUI層には必要以上の情報を返さない事を意識します。 30 | * @return 振込出金依頼ID 31 | */ 32 | ResponseEntity withdraw(RegCashOut p); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/AssetFacadeExporter.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.api; 2 | 3 | import static sample.microasset.api.AssetFacade.Path; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import sample.api.ApiUtils; 13 | import sample.microasset.model.asset.CashInOut; 14 | import sample.microasset.model.asset.CashInOut.RegCashOut; 15 | import sample.microasset.usecase.AssetService; 16 | 17 | /** 18 | * 資産系ユースケースの外部公開処理を表現します。 19 | */ 20 | @RestController 21 | @RequestMapping(Path) 22 | public class AssetFacadeExporter implements AssetFacade { 23 | 24 | private final AssetService service; 25 | 26 | public AssetFacadeExporter(AssetService service) { 27 | this.service = service; 28 | } 29 | 30 | /** {@inheritDoc} */ 31 | @Override 32 | @GetMapping(PathFindUnprocessedCashOut) 33 | public List findUnprocessedCashOut() { 34 | return service.findUnprocessedCashOut(); 35 | } 36 | 37 | /** {@inheritDoc} */ 38 | @Override 39 | @PostMapping(PathWithdraw) 40 | public ResponseEntity withdraw(@RequestBody @Valid RegCashOut p) { 41 | return ApiUtils.result(() -> service.withdraw(p)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/admin/AssetAdminFacade.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.api.admin; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | 7 | import sample.microasset.model.asset.CashInOut; 8 | import sample.microasset.model.asset.CashInOut.FindCashInOut; 9 | 10 | /** 11 | * 資産ドメインに対する社内ユースケース API を表現します。 12 | */ 13 | public interface AssetAdminFacade { 14 | 15 | String Path = "/api/admin/asset"; 16 | 17 | String PathFindCashInOut = "/cio/"; 18 | String PathClosingCashOut = "/cio/closingCashOut"; 19 | String PathRealizeCashflow = "/cf/realizeCashflow"; 20 | 21 | /** 未処理の振込依頼情報を検索します。 */ 22 | List findCashInOut(FindCashInOut p); 23 | 24 | /** 25 | * 振込出金依頼を締めます。 26 | */ 27 | ResponseEntity closingCashOut(); 28 | 29 | /** 30 | * キャッシュフローを実現します。 31 | *

受渡日を迎えたキャッシュフローを残高に反映します。 32 | */ 33 | ResponseEntity realizeCashflow(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/admin/AssetAdminFacadeExporter.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.api.admin; 2 | 3 | import static sample.microasset.api.admin.AssetAdminFacade.Path; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import sample.api.ApiUtils; 13 | import sample.microasset.model.asset.CashInOut; 14 | import sample.microasset.model.asset.CashInOut.FindCashInOut; 15 | import sample.microasset.usecase.AssetAdminService; 16 | 17 | /** 18 | * 資産系社内ユースケースの外部公開処理を表現します。 19 | */ 20 | @RestController 21 | @RequestMapping(Path) 22 | public class AssetAdminFacadeExporter implements AssetAdminFacade { 23 | 24 | private final AssetAdminService service; 25 | 26 | public AssetAdminFacadeExporter(AssetAdminService service) { 27 | this.service = service; 28 | } 29 | 30 | /** {@inheritDoc} */ 31 | @Override 32 | @GetMapping(PathFindCashInOut) 33 | public List findCashInOut(@Valid FindCashInOut p) { 34 | return service.findCashInOut(p); 35 | } 36 | 37 | /** {@inheritDoc} */ 38 | @Override 39 | @PostMapping(PathClosingCashOut) 40 | public ResponseEntity closingCashOut() { 41 | return ApiUtils.resultEmpty(() -> service.closingCashOut()); 42 | } 43 | 44 | /** {@inheritDoc} */ 45 | @Override 46 | @PostMapping(PathRealizeCashflow) 47 | public ResponseEntity realizeCashflow() { 48 | return ApiUtils.resultEmpty(() -> service.realizeCashflow()); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/admin/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する社内 API コンポーネント。 3 | */ 4 | package sample.microasset.api.admin; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する API コンポーネント。 3 | */ 4 | package sample.microasset.api; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/context/orm/AssetRepository.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.context.orm; 2 | 3 | import javax.persistence.*; 4 | import javax.sql.DataSource; 5 | 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.orm.jpa.*; 8 | 9 | import lombok.*; 10 | import sample.context.orm.*; 11 | 12 | /** 資産スキーマのRepositoryを表現します。 */ 13 | @Setter 14 | public class AssetRepository extends OrmRepository { 15 | public static final String BeanNameDs = "assetDataSource"; 16 | public static final String BeanNameEmf = "assetEntityManagerFactory"; 17 | public static final String BeanNameTx = "assetTransactionManager"; 18 | 19 | @PersistenceContext(unitName = BeanNameEmf) 20 | private EntityManager em; 21 | 22 | @Override 23 | public EntityManager em() { 24 | return em; 25 | } 26 | 27 | /** 資産スキーマのHibernateコンポーネントを生成します。 */ 28 | @ConfigurationProperties(prefix = "extension.datasource.asset") 29 | @Data 30 | @EqualsAndHashCode(callSuper = false) 31 | public static class AssetDataSourceProperties extends OrmDataSourceProperties { 32 | private OrmRepositoryProperties jpa = new OrmRepositoryProperties(); 33 | 34 | public DataSource dataSource() { 35 | return super.dataSource(); 36 | } 37 | 38 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean( 39 | final DataSource dataSource) { 40 | return jpa.entityManagerFactoryBean(BeanNameEmf, dataSource); 41 | } 42 | 43 | public JpaTransactionManager transactionManager(final EntityManagerFactory emf) { 44 | return jpa.transactionManager(emf); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/Asset.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset; 2 | 3 | import java.math.BigDecimal; 4 | import java.time.LocalDate; 5 | 6 | import lombok.Getter; 7 | import sample.context.orm.OrmRepository; 8 | import sample.util.Calculator; 9 | 10 | /** 11 | * 口座の資産概念を表現します。 12 | * asset配下のEntityを横断的に取り扱います。 13 | * low: 実際の開発では多通貨や執行中/拘束中のキャッシュフローアクションに対する考慮で、サービスによってはかなり複雑になります。 14 | */ 15 | @Getter 16 | public class Asset { 17 | /** 口座ID */ 18 | private final String id; 19 | 20 | private Asset(String id) { 21 | this.id = id; 22 | } 23 | 24 | /** 口座IDに紐付く資産概念を返します。 */ 25 | public static Asset by(String accountId) { 26 | return new Asset(accountId); 27 | } 28 | 29 | /** 30 | * 振込出金可能か判定します。 31 | *

0 <= 口座残高 + 未実現キャッシュフロー - (出金依頼拘束額 + 出金依頼額) 32 | * low: 判定のみなのでscale指定は省略。余力金額を返す時はきちんと指定する 33 | */ 34 | public boolean canWithdraw(final OrmRepository rep, String currency, BigDecimal absAmount, LocalDate valueDay) { 35 | Calculator calc = Calculator.of(CashBalance.getOrNew(rep, id, currency).getAmount()); 36 | Cashflow.findUnrealize(rep, id, currency, valueDay).stream().forEach((cf) -> calc.add(cf.getAmount())); 37 | CashInOut.findUnprocessed(rep, id, currency, true).stream() 38 | .forEach((withdrawal) -> calc.add(withdrawal.getAbsAmount().negate())); 39 | calc.add(absAmount.negate()); 40 | return 0 <= calc.decimal().signum(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/AssetErrorKeys.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset; 2 | 3 | /** 4 | * 資産の審査例外で用いるメッセージキー定数。 5 | */ 6 | public interface AssetErrorKeys { 7 | 8 | /** 受渡日を迎えていないため実現できません */ 9 | String CashflowRealizeDay = "error.Cashflow.realizeDay"; 10 | /** 既に受渡日を迎えています */ 11 | String CashflowBeforeEqualsDay = "error.Cashflow.beforeEqualsDay"; 12 | 13 | /** 未到来の受渡日です */ 14 | String CashInOutAfterEqualsDay = "error.CashInOut.afterEqualsDay"; 15 | /** 既に発生日を迎えています */ 16 | String CashInOutBeforeEqualsDay = "error.CashInOut.beforeEqualsDay"; 17 | /** 出金可能額を超えています */ 18 | String CashInOutWithdrawAmount = "error.CashInOut.withdrawAmount"; 19 | } 20 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/Remarks.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset; 2 | 3 | /** 4 | * 摘要定数インターフェース。 5 | */ 6 | public interface Remarks { 7 | 8 | /** 振込入金 */ 9 | String CashIn = "cashIn"; 10 | /** 振込入金(調整) */ 11 | String CashInAdjust = "cashInAdjust"; 12 | /** 振込入金(取消) */ 13 | String CashInCancel = "cashInCancel"; 14 | /** 振込出金 */ 15 | String CashOut = "cashOut"; 16 | /** 振込出金(調整) */ 17 | String CashOutAdjust = "cashOutAdjust"; 18 | /** 振込出金(取消) */ 19 | String CashOutCancel = "cashOutCancel"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 資産に関連したドメイン概念。 3 | */ 4 | package sample.microasset.model.asset; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/type/CashflowType.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset.type; 2 | 3 | /** キャッシュフロー種別。 low: 各社固有です。摘要含めラベルはなるべくmessages.propertiesへ切り出し */ 4 | public enum CashflowType { 5 | /** 振込入金 */ 6 | CashIn, 7 | /** 振込出金 */ 8 | CashOut, 9 | /** 振替入金 */ 10 | CashTransferIn, 11 | /** 振替出金 */ 12 | CashTransferOut 13 | } 14 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/model/asset/type/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 資産に関連したドメイン列挙型。 3 | */ 4 | package sample.microasset.model.asset.type; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーションルートディレクトリ。 3 | *

Applicationクラスを実行する事でアプリケーションプロセスが立ち上がります。 4 | */ 5 | package sample.microasset; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/usecase/event/AppMailEvent.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.usecase.event; 2 | 3 | import lombok.*; 4 | import sample.context.Dto; 5 | 6 | /** 7 | * メール配信イベントを表現します。 8 | */ 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class AppMailEvent implements Dto { 13 | private static final long serialVersionUID = 1L; 14 | 15 | private AppMailType mailType; 16 | private T value; 17 | 18 | public static AppMailEvent of(AppMailType mailType, T value) { 19 | return new AppMailEvent(mailType, value); 20 | } 21 | 22 | /** メール配信種別を表現します。 */ 23 | public static enum AppMailType { 24 | /** 振込出金依頼の登録受付完了 */ 25 | FinishRequestWithdraw; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/usecase/mail/ServiceMailDeliver.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.usecase.mail; 2 | 3 | import org.springframework.context.event.EventListener; 4 | import org.springframework.stereotype.Component; 5 | 6 | import lombok.extern.slf4j.Slf4j; 7 | import sample.context.mail.MailHandler; 8 | import sample.microasset.model.asset.CashInOut; 9 | import sample.microasset.usecase.event.AppMailEvent; 10 | 11 | /** 12 | * アプリケーション層のサービスメール送信を行います。 13 | *

AppMailEvent に応じたメール配信をおこないます。 14 | */ 15 | @Component 16 | @Slf4j 17 | public class ServiceMailDeliver { 18 | @SuppressWarnings("unused") 19 | private final MailHandler mail; 20 | 21 | public ServiceMailDeliver(MailHandler mail) { 22 | this.mail = mail; 23 | } 24 | 25 | /** メール配信要求を受け付けます。 */ 26 | @EventListener(AppMailEvent.class) 27 | public void handleEvent(AppMailEvent event) { 28 | switch (event.getMailType()) { 29 | case FinishRequestWithdraw: 30 | sendFinishRequestWithdraw((CashInOut)event.getValue()); 31 | break; 32 | default: 33 | throw new IllegalStateException("サポートされないメール種別です。 [" + event + "]"); 34 | } 35 | } 36 | 37 | private void sendFinishRequestWithdraw(CashInOut cio) { 38 | //low: この例ではログのみ出力 39 | log.info("メール送信が行われました。 [" + cio + "]"); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/usecase/mail/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーション層のメールコンポーネント。 3 | *

出金依頼完了メール等、各ユースケースに紐付くメール送受信処理を管理します。 4 | */ 5 | package sample.microasset.usecase.mail; -------------------------------------------------------------------------------- /micro-asset/src/main/java/sample/microasset/usecase/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーション層のコンポーネント。 3 | *

UI層から呼び出され、ドメイン層のコンポーネントを利用してユースケース処理を実現します。 4 | * アプリケーション層のコンポーネントがUI層を呼び出す事やService同士で相互依存することは想定していません。 5 | */ 6 | package sample.microasset.usecase; -------------------------------------------------------------------------------- /micro-asset/src/main/resources/application-asset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: asset 4 | application.name: micro-asset 5 | 6 | server: 7 | port: 8100 8 | 9 | extension: 10 | datasource: 11 | asset: 12 | url: ${DB_ASSET_JDBC_URL:jdbc:h2:tcp://localhost:9092/mem:asset} 13 | username: ${DB_ASSET_JDBC_USERNAME:} 14 | password: ${DB_ASSET_JDBC_USERNAME:} 15 | jpa: 16 | package-to-scan: sample.microasset.model.asset 17 | hibernate.ddl-auto: create-drop 18 | datafixture.enabled: true 19 | 20 | 21 | --- 22 | spring: 23 | profiles: production 24 | 25 | extension: 26 | datasource: 27 | asset.jpa.hibernate.ddl-auto: none 28 | datafixture.enabled: false 29 | -------------------------------------------------------------------------------- /micro-asset/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ====================================================================================== 2 | Start Application [ micro-asset ] 3 | -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /micro-asset/src/test/java/sample/microasset/model/asset/AssetTest.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset; 2 | 3 | import static org.hamcrest.Matchers.is; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.LocalDate; 8 | 9 | import org.junit.Test; 10 | 11 | import sample.EntityTestSupport; 12 | import sample.model.account.Account; 13 | 14 | //low: 簡易な検証が中心 15 | public class AssetTest extends EntityTestSupport { 16 | 17 | @Override 18 | protected void setupPreset() { 19 | targetEntities(Account.class, CashBalance.class, Cashflow.class, CashInOut.class); 20 | } 21 | 22 | @Test 23 | public void 振込出金可能か判定する() { 24 | // 残高 + 未実現キャッシュフロー - 出金依頼拘束額 = 出金可能額 25 | // 10000 + (1000 - 2000) - 8000 = 1000 26 | tx(() -> { 27 | fixtures.acc("test").save(rep); 28 | fixturesAsset.cb("test", LocalDate.of(2014, 11, 18), "JPY", "10000").save(rep); 29 | fixturesAsset.cf("test", "1000", LocalDate.of(2014, 11, 18), LocalDate.of(2014, 11, 20)).save(rep); 30 | fixturesAsset.cf("test", "-2000", LocalDate.of(2014, 11, 19), LocalDate.of(2014, 11, 21)).save(rep); 31 | fixturesAsset.cio("test", "8000", true).save(rep); 32 | 33 | assertThat( 34 | Asset.by("test").canWithdraw(rep, "JPY", new BigDecimal("1000"), LocalDate.of(2014, 11, 21)), 35 | is(true)); 36 | assertThat( 37 | Asset.by("test").canWithdraw(rep, "JPY", new BigDecimal("1001"), LocalDate.of(2014, 11, 21)), 38 | is(false)); 39 | }); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /micro-asset/src/test/java/sample/microasset/model/asset/CashBalanceTest.java: -------------------------------------------------------------------------------- 1 | package sample.microasset.model.asset; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.assertThat; 5 | 6 | import java.math.BigDecimal; 7 | import java.time.LocalDate; 8 | 9 | import org.junit.Test; 10 | 11 | import sample.EntityTestSupport; 12 | import sample.microasset.model.asset.CashBalance; 13 | 14 | //low: 簡易な正常系検証のみ 15 | public class CashBalanceTest extends EntityTestSupport { 16 | 17 | @Override 18 | protected void setupPreset() { 19 | targetEntities(CashBalance.class); 20 | } 21 | 22 | @Test 23 | public void 現金残高を追加する() { 24 | LocalDate baseDay = businessDay.day(); 25 | tx(() -> { 26 | CashBalance cb = fixturesAsset.cb("test1", baseDay, "USD", "10.02").save(rep); 27 | 28 | // 10.02 + 11.51 = 21.53 29 | assertThat(cb.add(rep, new BigDecimal("11.51")).getAmount(), is(new BigDecimal("21.53"))); 30 | 31 | // 21.53 + 11.516 = 33.04 (端数切捨確認) 32 | assertThat(cb.add(rep, new BigDecimal("11.516")).getAmount(), is(new BigDecimal("33.04"))); 33 | 34 | // 33.04 - 41.51 = -8.47 (マイナス値/マイナス残許容) 35 | assertThat(cb.add(rep, new BigDecimal("-41.51")).getAmount(), is(new BigDecimal("-8.47"))); 36 | }); 37 | } 38 | 39 | @Test 40 | public void 現金残高を取得する() { 41 | LocalDate baseDay = businessDay.day(); 42 | LocalDate baseMinus1Day = businessDay.day(-1); 43 | tx(() -> { 44 | fixturesAsset.cb("test1", baseDay, "JPY", "1000").save(rep); 45 | fixturesAsset.cb("test2", baseMinus1Day, "JPY", "3000").save(rep); 46 | 47 | // 存在している残高の検証 48 | CashBalance cbNormal = CashBalance.getOrNew(rep, "test1", "JPY"); 49 | assertThat(cbNormal, allOf( 50 | hasProperty("accountId", is("test1")), 51 | hasProperty("baseDay", is(baseDay)), 52 | hasProperty("amount", is(new BigDecimal("1000"))))); 53 | 54 | // 基準日に存在していない残高の繰越検証 55 | CashBalance cbRoll = CashBalance.getOrNew(rep, "test2", "JPY"); 56 | assertThat(cbRoll, allOf( 57 | hasProperty("accountId", is("test2")), 58 | hasProperty("baseDay", is(baseDay)), 59 | hasProperty("amount", is(new BigDecimal("3000"))))); 60 | 61 | // 残高を保有しない口座の生成検証 62 | CashBalance cbNew = CashBalance.getOrNew(rep, "test3", "JPY"); 63 | assertThat(cbNew, allOf( 64 | hasProperty("accountId", is("test3")), 65 | hasProperty("baseDay", is(baseDay)), 66 | hasProperty("amount", is(BigDecimal.ZERO)))); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /micro-asset/src/test/java/sample/support/MockDomainHelper.java: -------------------------------------------------------------------------------- 1 | package sample.support; 2 | 3 | import java.time.Clock; 4 | import java.util.*; 5 | 6 | import sample.context.*; 7 | import sample.context.actor.ActorSession; 8 | 9 | /** モックテスト用のドメインヘルパー */ 10 | public class MockDomainHelper extends DomainHelper { 11 | 12 | private Map settingMap = new HashMap<>(); 13 | 14 | public MockDomainHelper() { 15 | this(Clock.systemDefaultZone()); 16 | } 17 | 18 | public MockDomainHelper(final Clock mockClock) { 19 | setActorSession(new ActorSession()); 20 | setTime(new Timestamper(mockClock)); 21 | setSettingHandler(new AppSettingHandler(settingMap)); 22 | } 23 | 24 | public MockDomainHelper setting(String id, String value) { 25 | settingMap.put(id, value); 26 | return this; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /micro-asset/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: test 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | eureka.client.enabled: false 8 | 9 | extension: 10 | datasource: 11 | default: 12 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 13 | username: 14 | password: 15 | jpa: 16 | show-sql: true 17 | hibernate.ddl-auto: create-drop 18 | system: 19 | url: jdbc:h2:mem:system;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 20 | username: 21 | password: 22 | jpa: 23 | show-sql: true 24 | hibernate.ddl-auto: create-drop 25 | asset: 26 | url: jdbc:h2:mem:asset;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 27 | username: 28 | password: 29 | jpa: 30 | show-sql: true 31 | package-to-scan: sample.microasset.model.asset 32 | hibernate.ddl-auto: create-drop 33 | security.auth.enabled: false 34 | datafixture.enabled: false 35 | -------------------------------------------------------------------------------- /micro-asset/src/test/resources/application-testweb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: testweb 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | eureka.client.enabled: false 8 | 9 | extension: 10 | datasource: 11 | default: 12 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 13 | username: 14 | password: 15 | jpa.hibernate.ddl-auto: none 16 | system: 17 | url: jdbc:h2:mem:system;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 18 | username: 19 | password: 20 | jpa.hibernate.ddl-auto: none 21 | asset: 22 | url: jdbc:h2:mem:asset;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 23 | username: 24 | password: 25 | jpa: 26 | package-to-scan: sample.microasset.model.asset 27 | hibernate.ddl-auto: none 28 | security.auth.enabled: false 29 | -------------------------------------------------------------------------------- /micro-asset/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /micro-core/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /micro-core/README.md: -------------------------------------------------------------------------------- 1 | micro-core 2 | --- 3 | 4 | 各プロセス ( micro-registry を除く ) 間で共有されるクラスをライブラリとして管理します。 5 | 6 | - `Executable Jar` でなく、単純なライブラリとしての `jar` を生成します。 7 | -------------------------------------------------------------------------------- /micro-core/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar.enabled=false 2 | jar.enabled=true 3 | -------------------------------------------------------------------------------- /micro-core/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-core/libs/.gitkeep -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/ActionStatusType.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 何らかの行為に関わる処理ステータス概念。 7 | */ 8 | public enum ActionStatusType { 9 | /** 未処理 */ 10 | Unprocessed, 11 | /** 処理中 */ 12 | Processing, 13 | /** 処理済 */ 14 | Processed, 15 | /** 取消 */ 16 | Cancelled, 17 | /** エラー */ 18 | Error; 19 | 20 | /** 完了済みのステータス一覧 */ 21 | public static final List finishTypes = Collections.unmodifiableList( 22 | Arrays.asList(Processed, Cancelled)); 23 | 24 | /** 未完了のステータス一覧(処理中は含めない) */ 25 | public static final List unprocessingTypes = Collections.unmodifiableList( 26 | Arrays.asList(Unprocessed, Error)); 27 | 28 | /** 未完了のステータス一覧(処理中も含める) */ 29 | public static final List unprocessedTypes = Collections.unmodifiableList( 30 | Arrays.asList(Unprocessed, Processing, Error)); 31 | 32 | /** 完了済みのステータスの時はtrue */ 33 | public boolean isFinish() { 34 | return finishTypes.contains(this); 35 | } 36 | 37 | /** 未完了のステータス(処理中は含めない)の時はtrue */ 38 | public boolean isUnprocessing() { 39 | return unprocessingTypes.contains(this); 40 | } 41 | 42 | /** 未完了のステータス(処理中も含める)の時はtrue */ 43 | public boolean isUnprocessed() { 44 | return unprocessedTypes.contains(this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/InvocationException.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | /** 4 | * 処理時の実行例外を表現します。 5 | *

復旧不可能なシステム例外をラップする目的で利用してください。 6 | */ 7 | public class InvocationException extends RuntimeException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | public InvocationException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public InvocationException(String message) { 16 | super(message); 17 | } 18 | 19 | public InvocationException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/ApiClient.java: -------------------------------------------------------------------------------- 1 | package sample.api; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.web.client.RestTemplate; 6 | 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | 9 | import sample.context.rest.RestInvoker; 10 | 11 | /** 12 | * Spring Cloud 標準の API クライアント要求アプローチをサポートします。 13 | *

API クライアント側の Facade で本コンポーネントから RestInvoker を取得して実行してください。 14 | */ 15 | public class ApiClient { 16 | 17 | private final RestTemplate template; 18 | private final ObjectMapper mapper; 19 | 20 | public ApiClient(RestTemplate template, ObjectMapper mapper) { 21 | this.template = template; 22 | this.mapper = mapper; 23 | } 24 | 25 | /** RestInvoker を返します。 */ 26 | public RestInvoker invoker(String url, String rootPath) { 27 | return new RestInvoker(this.template, this.mapper, rootUrl(url, rootPath)); 28 | } 29 | 30 | /** API 接続先ルートとなる URL を返します。 */ 31 | private String rootUrl(String url, String rootPath) { 32 | return url + Optional.ofNullable(rootPath).orElse(""); 33 | } 34 | 35 | public static ApiClient of(RestTemplate template, ObjectMapper mapper) { 36 | return new ApiClient(template, mapper); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/ApiUtils.java: -------------------------------------------------------------------------------- 1 | package sample.api; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import org.springframework.http.*; 6 | 7 | /** APIで利用されるユーティリティを表現します。 */ 8 | public abstract class ApiUtils { 9 | 10 | /** 戻り値を生成して返します。(戻り値がプリミティブまたはnullを許容する時はこちらを利用してください) */ 11 | public static ResponseEntity result(Supplier command) { 12 | return ResponseEntity.status(HttpStatus.OK).body(command.get()); 13 | } 14 | 15 | /** 空の戻り値を生成して返します。 */ 16 | public static ResponseEntity resultEmpty(Runnable command) { 17 | command.run(); 18 | return ResponseEntity.status(HttpStatus.OK).build(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/admin/MasterAdminFacade.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | 7 | import sample.model.master.*; 8 | import sample.model.master.Holiday.RegHoliday; 9 | 10 | /** 11 | * マスタドメインに対する社内ユースケース API を表現します。 12 | */ 13 | public interface MasterAdminFacade { 14 | 15 | String Path = "/api/admin/master"; 16 | 17 | String PathGetStaff = "/staff/{staffId}/"; 18 | String PathFindStaffAuthority = "/staff/{staffId}/authority"; 19 | String PathRegisterHoliday = "/holiday"; 20 | 21 | /** 社員を取得します。 */ 22 | Staff getStaff(String staffId); 23 | 24 | /** 社員権限を取得します。 */ 25 | List findStaffAuthority(String staffId); 26 | 27 | /** 休日を登録します。 */ 28 | ResponseEntity registerHoliday(final RegHoliday p); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/admin/SystemAdminFacade.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.http.ResponseEntity; 6 | 7 | import sample.context.AppSetting; 8 | import sample.context.AppSetting.FindAppSetting; 9 | import sample.context.audit.*; 10 | import sample.context.audit.AuditActor.FindAuditActor; 11 | import sample.context.audit.AuditEvent.FindAuditEvent; 12 | import sample.context.orm.PagingList; 13 | 14 | /** 15 | * システムドメインに対する社内ユースケース API を表現します。 16 | */ 17 | public interface SystemAdminFacade { 18 | 19 | String Path = "/api/admin/system"; 20 | 21 | String PathFindAudiActor = "/audit/actor/"; 22 | String PathFindAudiEvent = "/audit/event/"; 23 | String PathFindAppSetting = "/setting/"; 24 | String PathChangeAppSetting = "/setting/{id}?value={value}"; 25 | String PathProcessDay = "/processDay"; 26 | 27 | /** 利用者監査ログを検索します。 */ 28 | PagingList findAuditActor(FindAuditActor p); 29 | 30 | /** イベント監査ログを検索します。 */ 31 | PagingList findAuditEvent(FindAuditEvent p); 32 | 33 | /** アプリケーション設定一覧を検索します。 */ 34 | List findAppSetting(FindAppSetting p); 35 | 36 | /** アプリケーション設定情報を変更します。 */ 37 | ResponseEntity changeAppSetting(String id, String value); 38 | 39 | /** 営業日を進めます。 */ 40 | ResponseEntity processDay(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/admin/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する社内 API コンポーネント。 3 | */ 4 | package sample.api.admin; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する API コンポーネント。 3 | */ 4 | package sample.api; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/AppSettingHandler.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.beans.factory.annotation.*; 6 | import org.springframework.cache.annotation.*; 7 | import org.springframework.transaction.PlatformTransactionManager; 8 | 9 | import sample.context.orm.*; 10 | 11 | /** 12 | * アプリケーション設定情報に対するアクセス手段を提供します。 13 | */ 14 | public class AppSettingHandler { 15 | 16 | @Autowired 17 | private SystemRepository rep; 18 | @Autowired 19 | @Qualifier(SystemRepository.BeanNameTx) 20 | private PlatformTransactionManager txm; 21 | /** 設定時は固定のキー/値を返すモックモードとする */ 22 | private final Optional> mockMap; 23 | 24 | public AppSettingHandler() { 25 | this.mockMap = Optional.empty(); 26 | } 27 | 28 | public AppSettingHandler(Map mockMap) { 29 | this.mockMap = Optional.of(mockMap); 30 | } 31 | 32 | /** アプリケーション設定情報を取得します。 */ 33 | @Cacheable(cacheNames = "AppSettingHandler.appSetting", key = "#id") 34 | public AppSetting setting(String id) { 35 | if (mockMap.isPresent()) { 36 | return mockSetting(id); 37 | } 38 | AppSetting setting = TxTemplate.of(txm).readOnly().tx( 39 | () -> AppSetting.load(rep, id)); 40 | return setting; 41 | } 42 | 43 | private AppSetting mockSetting(String id) { 44 | return new AppSetting(id, "category", "テスト用モック情報", mockMap.get().get(id)); 45 | } 46 | 47 | /** アプリケーション設定情報を変更します。 */ 48 | @CacheEvict(cacheNames = "AppSettingHandler.appSetting", key = "#id") 49 | public AppSetting update(String id, String value) { 50 | return mockMap.isPresent() ? mockSetting(id) 51 | : TxTemplate.of(txm).tx(() -> AppSetting.load(rep, id).update(rep, value)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/DomainHelper.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | 5 | import lombok.Setter; 6 | import sample.context.actor.*; 7 | 8 | /** 9 | * ドメイン処理を行う上で必要となるインフラ層コンポーネントへのアクセサを提供します。 10 | */ 11 | @Setter 12 | public class DomainHelper { 13 | 14 | @Autowired 15 | private ActorSession actorSession; 16 | @Autowired 17 | private Timestamper time; 18 | @Autowired 19 | private AppSettingHandler settingHandler; 20 | 21 | /** ログイン中のユースケース利用者を取得します。 */ 22 | public Actor actor() { 23 | return actorSession().actor(); 24 | } 25 | 26 | /** スレッドローカルスコープの利用者セッションを取得します。 */ 27 | public ActorSession actorSession() { 28 | return actorSession; 29 | } 30 | 31 | /** 日時ユーティリティを取得します。 */ 32 | public Timestamper time() { 33 | return time; 34 | } 35 | 36 | /** アプリケーション設定情報を取得します。 */ 37 | public AppSetting setting(String id) { 38 | return settingHandler.setting(id); 39 | } 40 | 41 | /** アプリケーション設定情報を設定します。 */ 42 | public AppSetting settingSet(String id, String value) { 43 | return settingHandler.update(id, value); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/Dto.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * DTO(Data Transfer Object)を表現するマーカーインターフェース。 7 | * 8 | *

本インターフェースを継承するDTOは、層(レイヤー)間をまたいで情報を取り扱い可能に 9 | * する役割を持ち、次の責務を果たします。 10 | *

    11 | *
  • 複数の情報の取りまとめによる通信コストの軽減 12 | *
  • 可変情報の集約 13 | *
  • ドメイン情報の転送 14 | *
  • ドメインロジックを持たない、シンプルなバリューオブジェクトの転送 15 | *
16 | */ 17 | public interface Dto extends Serializable { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/Entity.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | /** 4 | * ドメインオブジェクトのマーカーインターフェース。 5 | * 6 | *

本インターフェースを継承するドメインオブジェクトは、ある一定の粒度で とりまとめられたドメイン情報と、 7 | * それに関連するビジネスロジックを実行する役割を持ち、 次の責務を果たします。 8 | *

    9 | *
  • ドメイン情報の管理 10 | *
  • ドメイン情報に対する振る舞い 11 | *
12 | * 13 | *

14 | * ドメインモデルの詳細については次の書籍を参考にしてください。 15 | *

20 | */ 21 | public interface Entity { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/Repository.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import java.io.Serializable; 4 | import java.util.*; 5 | 6 | /** 7 | * 特定のドメインオブジェクトに依存しない汎用的なRepositoryです。 8 | *

タイプセーフでないRepositoryとして利用することができます。 9 | */ 10 | public interface Repository { 11 | 12 | /** 13 | * @return ドメイン層においてインフラ層コンポーネントへのアクセスを提供するヘルパーユーティリティを返します。 14 | */ 15 | DomainHelper dh(); 16 | 17 | /** 18 | * プライマリキーに一致する{@link Entity}を返します。 19 | * @param 戻り値の型 20 | * @param clazz 取得するインスタンスのクラス 21 | * @param id プライマリキー 22 | * @return プライマリキーに一致した{@link Entity}。 23 | */ 24 | Optional get(final Class clazz, final Serializable id); 25 | 26 | /** 27 | * プライマリキーに一致する{@link Entity}を返します。 28 | * @param 戻り値の型 29 | * @param clazz 取得するインスタンスのクラス 30 | * @param id プライマリキー 31 | * @return プライマリキーに一致した{@link Entity}。一致しない時は例外。 32 | */ 33 | T load(final Class clazz, final Serializable id); 34 | 35 | /** 36 | * プライマリキーに一致する{@link Entity}を返します。 37 | *

ロック付(for update)で取得を行うため、デッドロック回避を意識するようにしてください。 38 | * @param 戻り値の型 39 | * @param clazz 取得するインスタンスのクラス 40 | * @param id プライマリキー 41 | * @return プライマリキーに一致した{@link Entity}。一致しない時は例外。 42 | */ 43 | T loadForUpdate(final Class clazz, final Serializable id); 44 | 45 | /** 46 | * プライマリキーに一致する{@link Entity}が存在するか返します。 47 | * @param 確認型 48 | * @param clazz 対象クラス 49 | * @param id プライマリキー 50 | * @return 存在する時はtrue 51 | */ 52 | boolean exists(final Class clazz, final Serializable id); 53 | 54 | /** 55 | * 管理する{@link Entity}を全件返します。 56 | * 条件検索などは#templateを利用して実行するようにしてください。 57 | * @param 戻り値の型 58 | * @param clazz 取得するインスタンスのクラス 59 | * @return {@link Entity}一覧 60 | */ 61 | List findAll(final Class clazz); 62 | 63 | /** 64 | * {@link Entity}を新規追加します。 65 | * @param entity 追加対象{@link Entity} 66 | * @return 追加した{@link Entity}のプライマリキー 67 | */ 68 | T save(final T entity); 69 | 70 | /** 71 | * {@link Entity}を新規追加または更新します。 72 | *

既に同一のプライマリキーが存在するときは更新。 73 | * 存在しない時は新規追加となります。 74 | * @param entity 追加対象{@link Entity} 75 | */ 76 | T saveOrUpdate(final T entity); 77 | 78 | /** 79 | * {@link Entity}を更新します。 80 | * @param entity 更新対象{@link Entity} 81 | */ 82 | T update(final T entity); 83 | 84 | /** 85 | * {@link Entity}を削除します。 86 | * @param entity 削除対象{@link Entity} 87 | */ 88 | T delete(final T entity); 89 | 90 | } 91 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/ResourceBundleHandler.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.stream.Collectors; 6 | 7 | import org.springframework.boot.context.properties.ConfigurationProperties; 8 | import org.springframework.context.support.ResourceBundleMessageSource; 9 | 10 | /** 11 | * ResourceBundleに対する簡易アクセスを提供します。 12 | *

本コンポーネントはAPI経由でのラベル一覧の提供等、i18n用途のメッセージプロパティで利用してください。 13 | *

ResourceBundleは単純な文字列変換を目的とする標準のMessageSourceとは異なる特性(リスト概念)を 14 | * 持つため、別インスタンスでの管理としています。 15 | * (spring.messageとは別に指定[extension.messages]する必要があるので注意してください) 16 | */ 17 | @ConfigurationProperties(prefix = "extension.messages") 18 | public class ResourceBundleHandler { 19 | 20 | private String encoding = "UTF-8"; 21 | private Map bundleMap = new ConcurrentHashMap<>(); 22 | 23 | /** 24 | * 指定されたメッセージソースのResourceBundleを返します。 25 | *

basenameに拡張子(.properties)を含める必要はありません。 26 | */ 27 | public ResourceBundle get(String basename) { 28 | return get(basename, Locale.getDefault()); 29 | } 30 | 31 | public synchronized ResourceBundle get(String basename, Locale locale) { 32 | bundleMap.putIfAbsent(keyname(basename, locale), ResourceBundleFactory.create(basename, locale, encoding)); 33 | return bundleMap.get(keyname(basename, locale)); 34 | } 35 | 36 | private String keyname(String basename, Locale locale) { 37 | return basename + "_" + locale.toLanguageTag(); 38 | } 39 | 40 | /** 41 | * 指定されたメッセージソースのラベルキー、値のMapを返します。 42 | *

basenameに拡張子(.properties)を含める必要はありません。 43 | */ 44 | public Map labels(String basename) { 45 | return labels(basename, Locale.getDefault()); 46 | } 47 | 48 | public Map labels(String basename, Locale locale) { 49 | ResourceBundle bundle = get(basename, locale); 50 | return bundle.keySet().stream().collect(Collectors.toMap( 51 | key -> key, 52 | key -> bundle.getString(key))); 53 | } 54 | 55 | /** 56 | * SpringのMessageSource経由でResourceBundleを取得するFactory。 57 | *

プロパティファイルのエンコーディング指定を可能にしています。 58 | */ 59 | public static class ResourceBundleFactory extends ResourceBundleMessageSource { 60 | /** ResourceBundleを取得します。 */ 61 | public static ResourceBundle create(String basename, Locale locale, String encoding) { 62 | ResourceBundleFactory factory = new ResourceBundleFactory(); 63 | factory.setDefaultEncoding(encoding); 64 | return Optional.ofNullable(factory.getResourceBundle(basename, locale)) 65 | .orElseThrow(() -> new IllegalArgumentException("指定されたbasenameのリソースファイルは見つかりませんでした。[]")); 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/SimpleObjectProvider.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.beans.factory.ObjectProvider; 5 | 6 | /** 7 | * 保有するオブジェクトを単純に返す ObjectProvider。 8 | */ 9 | public class SimpleObjectProvider implements ObjectProvider { 10 | 11 | private final T target; 12 | 13 | public SimpleObjectProvider(T target) { 14 | this.target = target; 15 | } 16 | 17 | /** {@inheritDoc} */ 18 | @Override 19 | public T getObject() throws BeansException { 20 | return target; 21 | } 22 | 23 | /** {@inheritDoc} */ 24 | @Override 25 | public T getObject(Object... args) throws BeansException { 26 | return target; 27 | } 28 | 29 | /** {@inheritDoc} */ 30 | @Override 31 | public T getIfAvailable() throws BeansException { 32 | return target; 33 | } 34 | 35 | /** {@inheritDoc} */ 36 | @Override 37 | public T getIfUnique() throws BeansException { 38 | return target; 39 | } 40 | 41 | public static SimpleObjectProvider of(T target) { 42 | return new SimpleObjectProvider(target); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/Timestamper.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import java.time.*; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import sample.util.*; 8 | 9 | /** 10 | * 日時ユーティリティコンポーネント。 11 | */ 12 | public class Timestamper { 13 | public static final String KeyDay = "system.businessDay.day"; 14 | 15 | @Autowired(required = false) 16 | private AppSettingHandler setting; 17 | 18 | private final Clock clock; 19 | 20 | public Timestamper() { 21 | clock = Clock.systemDefaultZone(); 22 | } 23 | 24 | public Timestamper(final Clock clock) { 25 | this.clock = clock; 26 | } 27 | 28 | /** 営業日を返します。 */ 29 | public LocalDate day() { 30 | return setting == null ? LocalDate.now(clock) : DateUtils.day(setting.setting(KeyDay).str()); 31 | } 32 | 33 | /** 日時を返します。 */ 34 | public LocalDateTime date() { 35 | return LocalDateTime.now(clock); 36 | } 37 | 38 | /** 営業日/日時を返します。 */ 39 | public TimePoint tp() { 40 | return TimePoint.of(day(), date()); 41 | } 42 | 43 | /** 44 | * 営業日を指定日へ進めます。 45 | *

AppSettingHandlerを設定時のみ有効です。 46 | * @param day 更新営業日 47 | */ 48 | public Timestamper proceedDay(LocalDate day) { 49 | if (setting != null) 50 | setting.update(KeyDay, DateUtils.dayFormat(day)); 51 | return this; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/actor/Actor.java: -------------------------------------------------------------------------------- 1 | package sample.context.actor; 2 | 3 | import java.util.Locale; 4 | 5 | import lombok.*; 6 | import sample.context.Dto; 7 | 8 | /** 9 | * ユースケースにおける利用者を表現します。 10 | */ 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Actor implements Dto { 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 匿名利用者定数 */ 18 | public static Actor Anonymous = new Actor("unknown", ActorRoleType.Anonymous); 19 | /** システム利用者定数 */ 20 | public static Actor System = new Actor("system", ActorRoleType.System); 21 | 22 | /** 利用者ID */ 23 | private String id; 24 | /** 利用者名称 */ 25 | private String name; 26 | /** 利用者が持つ{@link ActorRoleType} */ 27 | private ActorRoleType roleType; 28 | /** 利用者が使用する{@link Locale} */ 29 | private Locale locale; 30 | /** 利用者の接続チャネル名称 */ 31 | private String channel; 32 | /** 利用者を特定する外部情報。(IPなど) */ 33 | private String source; 34 | 35 | public Actor(String id, ActorRoleType roleType) { 36 | this(id, id, roleType); 37 | } 38 | 39 | public Actor(String id, String name, ActorRoleType roleType) { 40 | this(id, name, roleType, Locale.getDefault(), null, null); 41 | } 42 | 43 | /** 44 | * 利用者の役割を表現します。 45 | */ 46 | public static enum ActorRoleType { 47 | /** 匿名利用者(ID等の特定情報を持たない利用者) */ 48 | Anonymous, 49 | /** 利用者(主にBtoCの顧客, BtoB提供先社員) */ 50 | User, 51 | /** 内部利用者(主にBtoCの社員, BtoB提供元社員) */ 52 | Internal, 53 | /** システム管理者(ITシステム担当社員またはシステム管理会社の社員) */ 54 | Administrator, 55 | /** システム(システム上の自動処理) */ 56 | System; 57 | 58 | public boolean isAnonymous() { 59 | return this == Anonymous; 60 | } 61 | 62 | public boolean isSystem() { 63 | return this == System; 64 | } 65 | 66 | public boolean notSystem() { 67 | return !isSystem(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/actor/ActorSession.java: -------------------------------------------------------------------------------- 1 | package sample.context.actor; 2 | 3 | /** 4 | * スレッドローカルスコープの利用者セッション。 5 | */ 6 | public class ActorSession { 7 | 8 | private ThreadLocal actorLocal = new ThreadLocal<>(); 9 | 10 | /** 利用者セッションへ利用者を紐付けます。 */ 11 | public ActorSession bind(final Actor actor) { 12 | actorLocal.set(actor); 13 | return this; 14 | } 15 | 16 | /** 利用者セッションを破棄します。 */ 17 | public ActorSession unbind() { 18 | actorLocal.remove(); 19 | return this; 20 | } 21 | 22 | /** 有効な利用者を返します。紐付けされていない時は匿名者が返されます。 */ 23 | public Actor actor() { 24 | Actor actor = actorLocal.get(); 25 | return actor != null ? actor : Actor.Anonymous; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/actor/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 利用者関連のインフラ層コンポーネント。 3 | *

ユースケース実行時の利用者概念を提供します。 4 | */ 5 | package sample.context.actor; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/audit/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 監査証跡関連のインフラ層コンポーネント。 3 | *

ユースケース実行時の証跡管理をします。 4 | */ 5 | package sample.context.audit; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/lock/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ロック関連のインフラ層コンポーネント。 3 | */ 4 | package sample.context.lock; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/mail/MailHandler.java: -------------------------------------------------------------------------------- 1 | package sample.context.mail; 2 | 3 | /** 4 | * メール処理を行います。 5 | * low: サンプルでは概念クラスだけ提供します。実装はSpringが提供するメールコンポーネントを利用してください。 6 | */ 7 | public class MailHandler { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/mail/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * メール送受信関連のインフラ層コンポーネント。 3 | */ 4 | package sample.context.mail; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/DefaultRepository.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import javax.persistence.*; 4 | import javax.sql.DataSource; 5 | 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.orm.jpa.*; 8 | 9 | import lombok.*; 10 | 11 | /** 標準スキーマのRepositoryを表現します。 */ 12 | @Setter 13 | public class DefaultRepository extends OrmRepository { 14 | public static final String BeanNameDs = "dataSource"; 15 | public static final String BeanNameEmf = "entityManagerFactory"; 16 | public static final String BeanNameTx = "transactionManager"; 17 | 18 | @PersistenceContext(unitName = BeanNameEmf) 19 | private EntityManager em; 20 | 21 | @Override 22 | public EntityManager em() { 23 | return em; 24 | } 25 | 26 | /** 標準スキーマのDataSourceを生成します。 */ 27 | @ConfigurationProperties(prefix = "extension.datasource.default") 28 | @Data 29 | @EqualsAndHashCode(callSuper = false) 30 | public static class DefaultDataSourceProperties extends OrmDataSourceProperties { 31 | private OrmRepositoryProperties jpa = new OrmRepositoryProperties(); 32 | 33 | public DataSource dataSource() { 34 | return super.dataSource(); 35 | } 36 | 37 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean( 38 | final DataSource dataSource) { 39 | return jpa.entityManagerFactoryBean(BeanNameEmf, dataSource); 40 | } 41 | 42 | public JpaTransactionManager transactionManager(final EntityManagerFactory emf) { 43 | return jpa.transactionManager(emf); 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmActiveMetaRecord.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.io.Serializable; 4 | import java.time.LocalDateTime; 5 | 6 | import sample.context.Entity; 7 | 8 | /** 9 | * OrmActiveRecordに登録/変更メタ概念を付与した基底クラス。 10 | * 本クラスを継承して作成されたEntityは永続化時に自動的なメタ情報更新が行われます。 11 | * @see OrmInterceptor 12 | */ 13 | public abstract class OrmActiveMetaRecord extends OrmActiveRecord implements Serializable, Entity { 14 | private static final long serialVersionUID = 1L; 15 | 16 | /** 登録利用者ID */ 17 | public abstract String getCreateId(); 18 | 19 | public abstract void setCreateId(String createId); 20 | 21 | /** 登録日時 */ 22 | public abstract LocalDateTime getCreateDate(); 23 | 24 | public abstract void setCreateDate(LocalDateTime createDate); 25 | 26 | /** 更新利用者ID */ 27 | public abstract String getUpdateId(); 28 | 29 | public abstract void setUpdateId(String updateId); 30 | 31 | /** 更新日時 */ 32 | public abstract LocalDateTime getUpdateDate(); 33 | 34 | public abstract void setUpdateDate(LocalDateTime updateDate); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmActiveRecord.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.io.Serializable; 4 | import java.util.function.Consumer; 5 | 6 | import sample.context.Entity; 7 | import sample.util.Validator; 8 | 9 | /** 10 | * ORMベースでActiveRecordの概念を提供するEntity基底クラス。 11 | *

ここでは自インスタンスの状態に依存する簡易な振る舞いのみをサポートします。 12 | * 実際のActiveRecordモデルにはget/find等の概念も含まれますが、それらは 自己の状態を 13 | * 変える行為ではなく対象インスタンスを特定する行為(クラス概念)にあたるため、 14 | * クラスメソッドとして継承先で個別定義するようにしてください。 15 | *

16 |  * public static Optional<Account> get(final OrmRepository rep, String id) {
17 |  *     return rep.get(Account.class, id);
18 |  * }
19 |  * 
20 |  * public static Account findAll(final OrmRepository rep) {
21 |  *     return rep.findAll(Account.class);
22 |  * }
23 |  * 
24 | */ 25 | public class OrmActiveRecord implements Serializable, Entity { 26 | private static final long serialVersionUID = 1L; 27 | 28 | /** 審査処理をします。 */ 29 | @SuppressWarnings("unchecked") 30 | protected T validate(Consumer proc) { 31 | Validator.validate(proc); 32 | return (T) this; 33 | } 34 | 35 | /** 36 | * 与えられたレポジトリを経由して自身を新規追加します。 37 | * @param rep 永続化の際に利用する関連{@link OrmRepository} 38 | * @return 自身の情報 39 | */ 40 | @SuppressWarnings("unchecked") 41 | public T save(final OrmRepository rep) { 42 | return (T) rep.save(this); 43 | } 44 | 45 | /** 46 | * 与えられたレポジトリを経由して自身を更新します。 47 | * @param rep 永続化の際に利用する関連{@link OrmRepository} 48 | */ 49 | @SuppressWarnings("unchecked") 50 | public T update(final OrmRepository rep) { 51 | return (T) rep.update(this); 52 | } 53 | 54 | /** 55 | * 与えられたレポジトリを経由して自身を物理削除します。 56 | * @param rep 永続化の際に利用する関連{@link OrmRepository} 57 | */ 58 | @SuppressWarnings("unchecked") 59 | public T delete(final OrmRepository rep) { 60 | return (T) rep.delete(this); 61 | } 62 | 63 | /** 64 | * 与えられたレポジトリを経由して自身を新規追加または更新します。 65 | * @param rep 永続化の際に利用する関連{@link OrmRepository} 66 | */ 67 | @SuppressWarnings("unchecked") 68 | public T saveOrUpdate(final OrmRepository rep) { 69 | return (T) rep.saveOrUpdate(this); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmDataSourceProperties.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.util.Properties; 4 | 5 | import javax.sql.DataSource; 6 | 7 | import org.springframework.boot.jdbc.DatabaseDriver; 8 | import org.springframework.util.StringUtils; 9 | 10 | import com.zaxxer.hikari.HikariConfig; 11 | import com.zaxxer.hikari.HikariDataSource; 12 | 13 | import lombok.Data; 14 | 15 | /** 16 | * DataSource生成用の設定クラス。 17 | *

継承先で@ConfigurationProperties定義を行ってapplication.ymlと紐付してください。 18 | *

ベース実装にHikariCPを利用しています。必要に応じて設定可能フィールドを増やすようにしてください。 19 | */ 20 | @Data 21 | public class OrmDataSourceProperties { 22 | 23 | /** ドライバクラス名称 ( 未設定時は url から自動登録 ) */ 24 | private String driverClassName; 25 | private String url; 26 | private String username; 27 | private String password; 28 | private Properties props = new Properties(); 29 | 30 | /** 最低接続プーリング数 */ 31 | private int minIdle = 1; 32 | /** 最大接続プーリング数 */ 33 | private int maxPoolSize = 20; 34 | 35 | /** コネクション状態を確認する時は true */ 36 | private boolean validation = true; 37 | /** コネクション状態確認クエリ ( 未設定時かつ Database が対応している時は自動設定 ) */ 38 | private String validationQuery; 39 | 40 | public DataSource dataSource() { 41 | HikariConfig config = new HikariConfig(); 42 | config.setDriverClassName(driverClassName()); 43 | config.setJdbcUrl(url); 44 | config.setUsername(username); 45 | config.setPassword(password); 46 | config.setMinimumIdle(minIdle); 47 | config.setMaximumPoolSize(maxPoolSize); 48 | if (validation) { 49 | config.setConnectionTestQuery(validationQuery()); 50 | } 51 | config.setDataSourceProperties(props); 52 | return new HikariDataSource(config); 53 | } 54 | 55 | private String driverClassName() { 56 | if (StringUtils.hasText(driverClassName)) { 57 | return driverClassName; 58 | } 59 | return DatabaseDriver.fromJdbcUrl(url).getDriverClassName(); 60 | } 61 | 62 | private String validationQuery() { 63 | if (StringUtils.hasText(validationQuery)) { 64 | return validationQuery; 65 | } 66 | return DatabaseDriver.fromJdbcUrl(url).getValidationQuery(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmInterceptor.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | 7 | import lombok.*; 8 | import sample.context.Timestamper; 9 | import sample.context.actor.*; 10 | 11 | /** 12 | * Entityの永続化タイミングでAOP処理を差し込む Interceptor。 13 | */ 14 | @Getter 15 | @Setter 16 | public class OrmInterceptor { 17 | 18 | @Autowired 19 | private ActorSession session; 20 | @Autowired 21 | private Timestamper time; 22 | 23 | /** 登録時の事前差し込み処理を行います。 */ 24 | public void touchForCreate(Object entity) { 25 | if (entity instanceof OrmActiveMetaRecord) { 26 | Actor staff = session.actor(); 27 | LocalDateTime now = time.date(); 28 | OrmActiveMetaRecord metaEntity = (OrmActiveMetaRecord) entity; 29 | metaEntity.setCreateId(staff.getId()); 30 | metaEntity.setCreateDate(now); 31 | metaEntity.setUpdateId(staff.getId()); 32 | metaEntity.setUpdateDate(now); 33 | } 34 | } 35 | 36 | /** 変更時の事前差し込み処理を行います。 */ 37 | public boolean touchForUpdate(final Object entity) { 38 | if (entity instanceof OrmActiveMetaRecord) { 39 | Actor staff = session.actor(); 40 | LocalDateTime now = time.date(); 41 | OrmActiveMetaRecord metaEntity = (OrmActiveMetaRecord) entity; 42 | if (metaEntity.getCreateDate() == null) { 43 | metaEntity.setCreateId(staff.getId()); 44 | metaEntity.setCreateDate(now); 45 | } 46 | metaEntity.setUpdateId(staff.getId()); 47 | metaEntity.setUpdateDate(now); 48 | } 49 | return false; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmQueryMetadata.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.util.*; 4 | 5 | import javax.persistence.LockModeType; 6 | 7 | /** 8 | * Query 向けの追加メタ情報を構築します。 9 | */ 10 | public class OrmQueryMetadata { 11 | 12 | private final Map hints = new HashMap<>(); 13 | private Optional lockMode = Optional.empty(); 14 | 15 | private OrmQueryMetadata() {} 16 | 17 | /** 内部に保持するヒント情報を返します。 */ 18 | public Map hints() { 19 | return hints; 20 | } 21 | 22 | /** 内部に保持するロックモードを返します。 */ 23 | public Optional lockMode() { 24 | return lockMode; 25 | } 26 | 27 | /** ヒントを追加します。 */ 28 | public OrmQueryMetadata hint(String hintName, Object value) { 29 | this.hints.put(hintName, value); 30 | return this; 31 | } 32 | 33 | /** ロックモードを設定します。 */ 34 | public OrmQueryMetadata lockMode(LockModeType lockMode) { 35 | this.lockMode = Optional.ofNullable(lockMode); 36 | return this; 37 | } 38 | 39 | public static OrmQueryMetadata empty() { 40 | return new OrmQueryMetadata(); 41 | } 42 | 43 | public static OrmQueryMetadata withLock(LockModeType lockMode) { 44 | return empty().lockMode(lockMode); 45 | } 46 | 47 | public static OrmQueryMetadata withHint(String hintName, Object value) { 48 | return empty().hint(hintName, value); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/OrmUtils.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import static java.util.regex.Pattern.*; 4 | 5 | import java.io.Serializable; 6 | import java.util.regex.*; 7 | 8 | import javax.persistence.EntityManager; 9 | 10 | import org.springframework.data.jpa.repository.support.*; 11 | import org.springframework.util.*; 12 | 13 | /** 14 | * Orm 関連のユーティリティを提供します。 15 | */ 16 | public abstract class OrmUtils { 17 | 18 | private static final String IDENTIFIER = "[._[\\P{Z}&&\\P{Cc}&&\\P{Cf}&&\\P{P}]]+"; 19 | private static final String IDENTIFIER_GROUP = String.format("(%s)", IDENTIFIER); 20 | private static final Pattern COUNT_MATCH; 21 | private static final int VARIABLE_NAME_GROUP_INDEX = 4; 22 | private static final String SIMPLE_COUNT_VALUE = "$2"; 23 | private static final String COMPLEX_COUNT_VALUE = "$3$6"; 24 | private static final String COUNT_REPLACEMENT_TEMPLATE = "select count(%s) $5$6$7"; 25 | private static final String ORDER_BY_PART = "(?iu)\\s+order\\s+by\\s+.*$"; 26 | 27 | static { 28 | StringBuilder builder = new StringBuilder(); 29 | builder.append("(select\\s+((distinct )?(.+?)?)\\s+)?(from\\s+"); 30 | builder.append(IDENTIFIER); 31 | builder.append("(?:\\s+as)?\\s+)"); 32 | builder.append(IDENTIFIER_GROUP); 33 | builder.append("(.*)"); 34 | COUNT_MATCH = compile(builder.toString(), CASE_INSENSITIVE); 35 | } 36 | 37 | /** 指定したクラスのエンティティ情報を返します ( ID 概念含む ) */ 38 | @SuppressWarnings("unchecked") 39 | public static JpaEntityInformation entityInformation(EntityManager em, Class clazz) { 40 | return (JpaEntityInformation)JpaEntityInformationSupport.getEntityInformation(clazz, em); 41 | } 42 | 43 | /** カウントクエリを生成します。 see QueryUtils#createCountQueryFor */ 44 | public static String createCountQueryFor(String originalQuery) { 45 | Assert.hasText(originalQuery, "OriginalQuery must not be null or empty!"); 46 | Matcher matcher = COUNT_MATCH.matcher(originalQuery); 47 | String variable = matcher.matches() ? matcher.group(VARIABLE_NAME_GROUP_INDEX) : null; 48 | boolean useVariable = variable != null && StringUtils.hasText(variable) && !variable.startsWith("new") 49 | && !variable.startsWith("count(") && !variable.contains(","); 50 | 51 | String replacement = useVariable ? SIMPLE_COUNT_VALUE : COMPLEX_COUNT_VALUE; 52 | String countQuery = matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, replacement)); 53 | return countQuery.replaceFirst(ORDER_BY_PART, ""); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/Pagination.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.math.RoundingMode; 4 | 5 | import lombok.*; 6 | import sample.context.Dto; 7 | import sample.context.orm.Sort.SortOrder; 8 | import sample.util.Calculator; 9 | 10 | /** 11 | * ページング情報を表現します。 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | public class Pagination implements Dto { 16 | private static final long serialVersionUID = 1l; 17 | public static final int DefaultSize = 100; 18 | /** ページ数(1開始) */ 19 | private int page; 20 | /** ページあたりの件数 */ 21 | private int size; 22 | /** トータル件数 */ 23 | private Long total; 24 | /** トータル件数算出を無視するか */ 25 | private boolean ignoreTotal; 26 | /** ソート条件 */ 27 | private Sort sort; 28 | 29 | public Pagination() { 30 | this(1); 31 | } 32 | 33 | public Pagination(int page) { 34 | this(page, DefaultSize, null, false, new Sort()); 35 | } 36 | 37 | public Pagination(int page, int size) { 38 | this(page, size, null, false, new Sort()); 39 | } 40 | 41 | public Pagination(int page, int size, final Sort sort) { 42 | this(page, size, null, false, sort); 43 | } 44 | 45 | public Pagination(final Pagination req, long total) { 46 | this(req.getPage(), req.getSize(), total, false, req.getSort()); 47 | } 48 | 49 | /** カウント算出を無効化します。 */ 50 | public Pagination ignoreTotal() { 51 | this.ignoreTotal = true; 52 | return this; 53 | } 54 | 55 | /** ソート指定が未指定の時は与えたソート条件で上書きします。 */ 56 | public Pagination sortIfEmpty(SortOrder... orders) { 57 | if (sort != null) 58 | sort.ifEmpty(orders); 59 | return this; 60 | } 61 | 62 | /** 最大ページ数を返します。total設定時のみ適切な値が返されます。 */ 63 | public int getMaxPage() { 64 | return (total == null) ? 0 : Calculator.of(total) 65 | .scale(0, RoundingMode.UP).divideBy(size).intValue(); 66 | } 67 | 68 | /** 開始件数を返します。 */ 69 | public int getFirstResult() { 70 | return (page - 1) * size; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/PagingList.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.util.List; 4 | 5 | import lombok.*; 6 | import sample.context.Dto; 7 | 8 | /** 9 | * ページング一覧を表現します。 10 | * 11 | * @param 結果オブジェクト(一覧の要素) 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class PagingList implements Dto { 17 | private static final long serialVersionUID = 1L; 18 | 19 | private List list; 20 | private Pagination page; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/Sort.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import java.io.Serializable; 4 | import java.util.*; 5 | 6 | import lombok.*; 7 | import sample.context.Dto; 8 | 9 | /** 10 | * ソート情報を表現します。 11 | * 複数件のソート情報(SortOrder)を内包します。 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class Sort implements Dto { 17 | private static final long serialVersionUID = 1L; 18 | 19 | /** ソート条件 */ 20 | private List orders = new ArrayList(); 21 | 22 | /** ソート条件を追加します。 */ 23 | public Sort add(SortOrder order) { 24 | orders.add(order); 25 | return this; 26 | } 27 | 28 | /** ソート条件(昇順)を追加します。 */ 29 | public Sort asc(String property) { 30 | return add(SortOrder.asc(property)); 31 | } 32 | 33 | /** ソート条件(降順)を追加します。 */ 34 | public Sort desc(String property) { 35 | return add(SortOrder.desc(property)); 36 | } 37 | 38 | /** ソート条件一覧を返します。 */ 39 | public List orders() { 40 | return orders; 41 | } 42 | 43 | /** ソート条件が未指定だった際にソート順が上書きされます。 */ 44 | public Sort ifEmpty(SortOrder... items) { 45 | if (orders.isEmpty() && items != null) { 46 | orders.addAll(Arrays.asList(items)); 47 | } 48 | return this; 49 | } 50 | 51 | /** 昇順でソート情報を返します。 */ 52 | public static Sort ascBy(String property) { 53 | return new Sort().asc(property); 54 | } 55 | 56 | /** 降順でソート情報を返します。 */ 57 | public static Sort descBy(String property) { 58 | return new Sort().desc(property); 59 | } 60 | 61 | /** フィールド単位のソート情報を表現します。 */ 62 | @Data 63 | @NoArgsConstructor 64 | @AllArgsConstructor 65 | public static class SortOrder implements Serializable { 66 | private static final long serialVersionUID = 1L; 67 | private String property; 68 | private boolean ascending; 69 | 70 | public static SortOrder asc(String property) { 71 | return new SortOrder(property, true); 72 | } 73 | 74 | public static SortOrder desc(String property) { 75 | return new SortOrder(property, false); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/SystemRepository.java: -------------------------------------------------------------------------------- 1 | package sample.context.orm; 2 | 3 | import javax.persistence.*; 4 | import javax.sql.DataSource; 5 | 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.orm.jpa.*; 8 | 9 | import lombok.*; 10 | 11 | /** システムスキーマのRepositoryを表現します。 */ 12 | @org.springframework.stereotype.Repository 13 | @Setter 14 | public class SystemRepository extends OrmRepository { 15 | 16 | public static final String BeanNameDs = "systemDataSource"; 17 | public static final String BeanNameEmf = "systemEntityManagerFactory"; 18 | public static final String BeanNameTx = "systemTransactionManager"; 19 | 20 | @PersistenceContext(unitName = BeanNameEmf) 21 | private EntityManager em; 22 | 23 | @Override 24 | public EntityManager em() { 25 | return em; 26 | } 27 | 28 | /** システムスキーマのDataSourceを生成します。 */ 29 | @ConfigurationProperties(prefix = "extension.datasource.system") 30 | @Data 31 | @EqualsAndHashCode(callSuper = false) 32 | public static class SystemDataSourceProperties extends OrmDataSourceProperties { 33 | private OrmRepositoryProperties jpa = new OrmRepositoryProperties(); 34 | 35 | public DataSource dataSource() { 36 | return super.dataSource(); 37 | } 38 | 39 | public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean( 40 | final DataSource dataSource) { 41 | return jpa.entityManagerFactoryBean(BeanNameEmf, dataSource); 42 | } 43 | 44 | public JpaTransactionManager transactionManager(final EntityManagerFactory emf) { 45 | return jpa.transactionManager(emf); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/orm/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ORM(Object Relational Mapping)関連のインフラ層コンポーネント。 3 | *

主にDB永続化をサポートしています。 4 | */ 5 | package sample.context.orm; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * インフラ層のコンポーネント。 3 | *

インフラ層のコンポーネントはUI層/アプリケーション層/ドメイン層から横断的に利用されます。 4 | */ 5 | package sample.context; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/report/ReportFile.java: -------------------------------------------------------------------------------- 1 | package sample.context.report; 2 | 3 | import lombok.*; 4 | import sample.context.Dto; 5 | 6 | /** ファイルイメージを表現します。 */ 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class ReportFile implements Dto { 11 | private static final long serialVersionUID = 1L; 12 | private String name; 13 | private byte[] data; 14 | 15 | public int size() { 16 | return data.length; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/report/ReportHandler.java: -------------------------------------------------------------------------------- 1 | package sample.context.report; 2 | 3 | /** 4 | * 帳票処理を行います。 5 | * low: サンプルでは概念クラスだけ提供します。実際はCSV/固定長/Excel/PDFなどの取込/出力を取り扱います。 6 | * low: ExcelはPOI、PDFはJasperReportの利用が一般的です。(商用製品を利用するのもおすすめです) 7 | */ 8 | public class ReportHandler { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/report/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 帳票関連のインフラ層コンポーネント。 3 | */ 4 | package sample.context.report; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/rest/RestActorSessionBindFilter.java: -------------------------------------------------------------------------------- 1 | package sample.context.rest; 2 | 3 | import java.io.IOException; 4 | import java.util.Optional; 5 | 6 | import javax.servlet.*; 7 | import javax.servlet.http.HttpServletRequest; 8 | 9 | import org.springframework.web.filter.GenericFilterBean; 10 | 11 | import sample.context.actor.ActorSession; 12 | import sample.context.rest.RestActorSessionInterceptor.RestActorSessionConverter; 13 | 14 | /** 15 | * プロセス間で ActorSession を引き継ぎさせる Filter。 (受付側) 16 | *

あらかじめ要求元プロセスに RestActorSessionInterceptor を適用しておく必要があります。 17 | *

非同期 Servlet を利用する場合は利用できません。( 別途同様の仕組みを作成してください ) 18 | */ 19 | public class RestActorSessionBindFilter extends GenericFilterBean { 20 | 21 | private final ActorSession session; 22 | 23 | public RestActorSessionBindFilter(ActorSession session) { 24 | this.session = session; 25 | } 26 | 27 | @Override 28 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 29 | throws IOException, ServletException { 30 | try { 31 | String actorStr = ((HttpServletRequest)request).getHeader(RestActorSessionInterceptor.AttrActorSession); 32 | Optional.ofNullable(actorStr).ifPresent((str) -> 33 | session.bind(RestActorSessionConverter.convert(str))); 34 | chain.doFilter(request, response); 35 | } finally { 36 | session.unbind(); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/rest/RestActorSessionInterceptor.java: -------------------------------------------------------------------------------- 1 | package sample.context.rest; 2 | 3 | import java.io.IOException; 4 | import java.util.*; 5 | 6 | import org.slf4j.*; 7 | import org.springframework.http.HttpRequest; 8 | import org.springframework.http.client.*; 9 | 10 | import sample.context.actor.*; 11 | import sample.context.actor.Actor.ActorRoleType; 12 | 13 | /** 14 | * HTTP リクエスト時にヘッダへ ActorSession 情報を紐付けする Interceptor。 15 | */ 16 | public class RestActorSessionInterceptor implements ClientHttpRequestInterceptor { 17 | 18 | public static final String AttrActorSession = "ActorSession"; 19 | 20 | private final ActorSession session; 21 | 22 | public RestActorSessionInterceptor(ActorSession session) { 23 | this.session = session; 24 | } 25 | 26 | @Override 27 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) 28 | throws IOException { 29 | request.getHeaders().add(AttrActorSession, RestActorSessionConverter.convert(session.actor())); 30 | return execution.execute(request, body); 31 | } 32 | 33 | /** HTTP ヘッダへ ActorSession を紐付けする際の変換条件を表現します。 */ 34 | public static class RestActorSessionConverter { 35 | private static final Logger logger = LoggerFactory.getLogger(RestActorSessionConverter.class); 36 | public static String convert(Actor actor) { 37 | return String.join("_", actor.getId(), actor.getName(), actor.getRoleType().name(), 38 | actor.getLocale().getLanguage(), 39 | Optional.ofNullable(actor.getChannel()).orElse("none"), 40 | Optional.ofNullable(actor.getSource()).orElse("none")); 41 | } 42 | public static Actor convert(String actorStr) { 43 | String[] params = actorStr.split("_"); 44 | if (params.length != 6) { 45 | logger.warn("Actor への変換に失敗しました。"); 46 | return Actor.Anonymous; 47 | } 48 | return new Actor(params[0], params[1], ActorRoleType.valueOf(params[2]), 49 | new Locale(params[3]), params[4], params[5]); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/rest/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * RESTfulAPI連携をサポートするインフラ層コンポーネント。 3 | *

Spring MVC の RestController や RestTemplate を補完しています。 4 | */ 5 | package sample.context.rest; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/security/SecurityProperties.java: -------------------------------------------------------------------------------- 1 | package sample.context.security; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * セキュリティ関連の設定情報を表現します。 9 | */ 10 | @Data 11 | @ConfigurationProperties(prefix = "extension.security") 12 | public class SecurityProperties { 13 | /** Spring Security依存の認証/認可設定情報 */ 14 | private SecurityAuthProperties auth = new SecurityAuthProperties(); 15 | /** CORS設定情報 */ 16 | private SecurityCorsProperties cors = new SecurityCorsProperties(); 17 | 18 | public SecurityAuthProperties auth() { 19 | return auth; 20 | } 21 | 22 | public SecurityCorsProperties cors() { 23 | return cors; 24 | } 25 | 26 | /** Spring Securityに対する拡張設定情報 */ 27 | @Data 28 | public static class SecurityAuthProperties { 29 | /** リクエスト時のログインIDを取得するキー */ 30 | private String loginKey = "loginId"; 31 | /** リクエスト時のパスワードを取得するキー */ 32 | private String passwordKey = "password"; 33 | /** 認証対象パス */ 34 | private String[] path = new String[] { "/api/**" }; 35 | /** 認証対象パス(管理者向け) */ 36 | private String[] pathAdmin = new String[] { "/api/admin/**" }; 37 | /** 認証除外パス(認証対象からの除外) */ 38 | private String[] excludesPath = new String[] { "/api/system/job/**" }; 39 | /** 認証無視パス(フィルタ未適用の認証未考慮、静的リソース等) */ 40 | private String[] ignorePath = new String[] { "/css/**", "/js/**", "/img/**", "/**/favicon.ico" }; 41 | /** ログインAPIパス */ 42 | private String loginPath = "/api/login"; 43 | /** ログアウトAPIパス */ 44 | private String logoutPath = "/api/logout"; 45 | /** 一人が同時利用可能な最大セッション数 */ 46 | private int maximumSessions = 2; 47 | /** 48 | * 社員向けモードの時はtrue。 49 | *

ログインパスは同じですが、ログイン処理の取り扱いが切り替わります。 50 | *

    51 | *
  • true: SecurityUserService 52 | *
  • false: SecurityAdminService 53 | *
54 | */ 55 | private boolean admin = false; 56 | /** 認証が有効な時はtrue */ 57 | private boolean enabled = true; 58 | } 59 | 60 | /** CORS設定情報を表現します。 */ 61 | @Data 62 | public static class SecurityCorsProperties { 63 | private boolean allowCredentials = true; 64 | private String allowedOrigin = "*"; 65 | private String allowedHeader = "*"; 66 | private String allowedMethod = "*"; 67 | private long maxAge = 3600L; 68 | private String path = "/**"; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/context/security/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 認証/認可関連のインフラ層コンポーネント。 3 | *

Spring Securityをパターン決め打ちで薄くラッピングしています。 4 | */ 5 | package sample.context.security; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/controller/RestErrorController.java: -------------------------------------------------------------------------------- 1 | package sample.controller; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.boot.web.servlet.error.*; 6 | import org.springframework.web.bind.annotation.*; 7 | import org.springframework.web.context.request.ServletWebRequest; 8 | 9 | /** 10 | * REST用の例外ハンドリングを行うController。 11 | *

application.ymlの"error.path"属性との組合せで有効化します。 12 | * あわせて"error.whitelabel.enabled: false"でwhitelabelを無効化しておく必要があります。 13 | * see ErrorMvcAutoConfiguration 14 | */ 15 | @RestController 16 | public class RestErrorController implements ErrorController { 17 | public static final String PathError = "/api/error"; 18 | 19 | private final ErrorAttributes errorAttributes; 20 | public RestErrorController(ErrorAttributes errorAttributes) { 21 | this.errorAttributes = errorAttributes; 22 | } 23 | 24 | @Override 25 | public String getErrorPath() { 26 | return PathError; 27 | } 28 | 29 | @RequestMapping(PathError) 30 | public Map error(ServletWebRequest request) { 31 | return this.errorAttributes.getErrorAttributes(request, false); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/controller/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * UI層のコンポーネントを管理します。 3 | *

アプリケーション層のコンポーネントを利用してユースケース処理を実現します。 4 | * UI層のコンポーネントが直接ドメイン層を呼び出す事やController同士で相互依存することは想定していません。
5 | * ※EntityについてはDTOとして解釈することで許容しますが、必要以上の情報を返してセキュリティリスクを生まぬよう注意する必要があります。 6 | */ 7 | package sample.controller; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/DomainErrorKeys.java: -------------------------------------------------------------------------------- 1 | package sample.model; 2 | 3 | /** 4 | * 汎用ドメインで用いるメッセージキー定数。 5 | */ 6 | public interface DomainErrorKeys { 7 | 8 | /** マイナスを含めない数字を入力してください */ 9 | String AbsAmountZero = "error.domain.AbsAmount.zero"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/account/FiAccount.java: -------------------------------------------------------------------------------- 1 | package sample.model.account; 2 | 3 | import javax.persistence.*; 4 | 5 | import lombok.*; 6 | import sample.context.orm.*; 7 | import sample.model.constraints.*; 8 | 9 | /** 10 | * 口座に紐づく金融機関口座を表現します。 11 | *

口座を相手方とする入出金で利用します。 12 | * low: サンプルなので支店や名称、名義といった本来必須な情報をかなり省略しています。(通常は全銀仕様を踏襲します) 13 | */ 14 | @Entity 15 | @Data 16 | @EqualsAndHashCode(callSuper = false) 17 | public class FiAccount extends OrmActiveRecord { 18 | private static final long serialVersionUID = 1L; 19 | 20 | /** ID */ 21 | @Id 22 | @GeneratedValue 23 | private Long id; 24 | /** 口座ID */ 25 | @IdStr 26 | private String accountId; 27 | /** 利用用途カテゴリ */ 28 | @Category 29 | private String category; 30 | /** 通貨 */ 31 | @Currency 32 | private String currency; 33 | /** 金融機関コード */ 34 | @IdStr 35 | private String fiCode; 36 | /** 金融機関口座ID */ 37 | @IdStr 38 | private String fiAccountId; 39 | 40 | public static FiAccount load(final OrmRepository rep, String accountId, String category, String currency) { 41 | return rep.tmpl().load("from FiAccount a where a.accountId=?1 and a.category=?2 and a.currency=?3", accountId, 42 | category, currency); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/account/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 口座に関連したドメイン概念。 3 | */ 4 | package sample.model.account; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/account/type/AccountStatusType.java: -------------------------------------------------------------------------------- 1 | package sample.model.account.type; 2 | 3 | /** 口座状態を表現します。 */ 4 | public enum AccountStatusType { 5 | /** 通常 */ 6 | Normal, 7 | /** 退会 */ 8 | Withdrawal; 9 | 10 | public boolean valid() { 11 | return this == Normal; 12 | } 13 | 14 | public boolean invalid() { 15 | return !valid(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/account/type/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 口座に関連したドメイン列挙型。 3 | */ 4 | package sample.model.account.type; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/AbsAmount.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 絶対値の金額(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotNull 20 | @Digits(integer = 16, fraction = 4) 21 | @DecimalMin("0.00") 22 | public @interface AbsAmount { 23 | String message() default "{error.domain.absAmount}"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | @OverridesAttribute(constraint = Digits.class, name = "integer") 30 | int integer() default 16; 31 | 32 | @OverridesAttribute(constraint = Digits.class, name = "fraction") 33 | int fraction() default 4; 34 | 35 | @OverridesAttribute(constraint = DecimalMin.class, name = "value") 36 | String min() default "0.00"; 37 | 38 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 39 | @Retention(RUNTIME) 40 | @Documented 41 | public @interface List { 42 | AbsAmount[] value(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/AbsAmountEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 絶対値の金額を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Digits(integer = 16, fraction = 4) 20 | @DecimalMin("0.00") 21 | public @interface AbsAmountEmpty { 22 | String message() default "{error.domain.absAmount}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @OverridesAttribute(constraint = Digits.class, name = "integer") 29 | int integer() default 16; 30 | 31 | @OverridesAttribute(constraint = Digits.class, name = "fraction") 32 | int fraction() default 4; 33 | 34 | @OverridesAttribute(constraint = DecimalMin.class, name = "value") 35 | String min() default "0.00"; 36 | 37 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 38 | @Retention(RUNTIME) 39 | @Documented 40 | public @interface List { 41 | AbsAmountEmpty[] value(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Amount.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 金額(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotNull 20 | @Digits(integer = 16, fraction = 4) 21 | public @interface Amount { 22 | String message() default "{error.domain.amount}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @OverridesAttribute(constraint = Digits.class, name = "integer") 29 | int integer() default 16; 30 | 31 | @OverridesAttribute(constraint = Digits.class, name = "fraction") 32 | int fraction() default 4; 33 | 34 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 35 | @Retention(RUNTIME) 36 | @Documented 37 | public @interface List { 38 | Amount[] value(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/AmountEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.Digits; 10 | 11 | /** 12 | * 金額を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Digits(integer = 16, fraction = 4) 20 | public @interface AmountEmpty { 21 | String message() default "{error.domain.amount}"; 22 | 23 | Class[] groups() default {}; 24 | 25 | Class[] payload() default {}; 26 | 27 | @OverridesAttribute(constraint = Digits.class, name = "integer") 28 | int integer() default 16; 29 | 30 | @OverridesAttribute(constraint = Digits.class, name = "fraction") 31 | int fraction() default 4; 32 | 33 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 34 | @Retention(RUNTIME) 35 | @Documented 36 | public @interface List { 37 | AmountEmpty[] value(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Category.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import sample.util.Regex; 12 | 13 | /** 14 | * 各種カテゴリ/区分(必須)を表現する制約注釈。 15 | */ 16 | @Documented 17 | @Constraint(validatedBy = {}) 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 19 | @Retention(RUNTIME) 20 | @ReportAsSingleViolation 21 | @NotBlank 22 | @Size 23 | @Pattern(regexp = "") 24 | public @interface Category { 25 | String message() default "{error.domain.category}"; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | 31 | @OverridesAttribute(constraint = Size.class, name = "max") 32 | int max() default 30; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 35 | String regexp() default Regex.rAscii; 36 | 37 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 38 | Pattern.Flag[] flags() default {}; 39 | 40 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 41 | @Retention(RUNTIME) 42 | @Documented 43 | public @interface List { 44 | Category[] value(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/CategoryEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.*; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.Pattern; 10 | import javax.validation.constraints.Size; 11 | 12 | import sample.util.Regex; 13 | 14 | /** 15 | * 各種カテゴリ/区分(必須)を表現する制約注釈。 16 | */ 17 | @Documented 18 | @Constraint(validatedBy = {}) 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 20 | @Retention(RUNTIME) 21 | @ReportAsSingleViolation 22 | @Size 23 | @Pattern(regexp = "") 24 | public @interface CategoryEmpty { 25 | String message() default "{error.domain.category}"; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | 31 | @OverridesAttribute(constraint = Size.class, name = "max") 32 | int max() default 30; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 35 | String regexp() default Regex.rAscii; 36 | 37 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 38 | Pattern.Flag[] flags() default {}; 39 | 40 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 41 | @Retention(RUNTIME) 42 | @Documented 43 | public @interface List { 44 | CategoryEmpty[] value(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Currency.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 通貨(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotBlank 20 | @Size 21 | @Pattern(regexp = "") 22 | public @interface Currency { 23 | String message() default "{error.domain.currency}"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | @OverridesAttribute(constraint = Size.class, name = "max") 30 | int max() default 3; 31 | 32 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 33 | String regexp() default "^[a-zA-Z]{3}$"; 34 | 35 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 36 | @Retention(RUNTIME) 37 | @Documented 38 | public @interface List { 39 | Currency[] value(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/CurrencyEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 通貨を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Size 20 | @Pattern(regexp = "") 21 | public @interface CurrencyEmpty { 22 | String message() default "{error.domain.currency}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @OverridesAttribute(constraint = Size.class, name = "max") 29 | int max() default 3; 30 | 31 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 32 | String regexp() default "^[a-zA-Z]{3}$"; 33 | 34 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 35 | @Retention(RUNTIME) 36 | @Documented 37 | public @interface List { 38 | CurrencyEmpty[] value(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Description.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 備考(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotBlank 20 | @Size 21 | @Pattern(regexp = "") 22 | public @interface Description { 23 | String message() default "{error.domain.description}"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | @OverridesAttribute(constraint = Size.class, name = "max") 30 | int max() default 400; 31 | 32 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 33 | String regexp() default ".*"; 34 | 35 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 36 | Pattern.Flag[] flags() default {}; 37 | 38 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 39 | @Retention(RUNTIME) 40 | @Documented 41 | public @interface List { 42 | Description[] value(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/DescriptionEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 備考を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Size 20 | @Pattern(regexp = "") 21 | public @interface DescriptionEmpty { 22 | String message() default "{error.domain.description}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @OverridesAttribute(constraint = Size.class, name = "max") 29 | int max() default 400; 30 | 31 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 32 | String regexp() default ".*"; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 35 | Pattern.Flag[] flags() default {}; 36 | 37 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 38 | @Retention(RUNTIME) 39 | @Documented 40 | public @interface List { 41 | DescriptionEmpty[] value(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Email.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * メールアドレス(必須)を表現する制約注釈。 13 | * low: とりあえずHibernateのEmailValidatorを利用しますが、恐らく最終的に 14 | * 固有のConstraintValidatorを作らされる事になると思います。 15 | */ 16 | @Documented 17 | @Constraint(validatedBy = {}) 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 19 | @Retention(RUNTIME) 20 | @ReportAsSingleViolation 21 | @NotBlank 22 | @Size 23 | @Pattern(regexp = "") 24 | public @interface Email { 25 | String message() default "{error.domain.email}"; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | 31 | @OverridesAttribute(constraint = Size.class, name = "max") 32 | int max() default 256; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 35 | String regexp() default ".*"; 36 | 37 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 38 | Pattern.Flag[] flags() default {}; 39 | 40 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 41 | @Retention(RUNTIME) 42 | @Documented 43 | public @interface List { 44 | Email[] value(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/EmailEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * メールアドレスを表現する制約注釈。 13 | * low: とりあえずHibernateのEmailValidatorを利用しますが、恐らく最終的に 14 | * 固有のConstraintValidatorを作らされる事になると思います。 15 | */ 16 | @Documented 17 | @Constraint(validatedBy = {}) 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 19 | @Retention(RUNTIME) 20 | @ReportAsSingleViolation 21 | @Size 22 | @Pattern(regexp = "") 23 | public @interface EmailEmpty { 24 | String message() default "{error.domain.email}"; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | 30 | @OverridesAttribute(constraint = Size.class, name = "max") 31 | int max() default 256; 32 | 33 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 34 | String regexp() default ".*"; 35 | 36 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 37 | Pattern.Flag[] flags() default {}; 38 | 39 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 40 | @Retention(RUNTIME) 41 | @Documented 42 | public @interface List { 43 | EmailEmpty[] value(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/ISODate.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | import org.springframework.format.annotation.DateTimeFormat.ISO; 13 | 14 | import com.fasterxml.jackson.annotation.JsonFormat; 15 | 16 | /** 17 | * ISOフォーマットの日付(必須)を表現する制約注釈。 18 | *

YYYY-MM-DDを想定します。 19 | */ 20 | @Documented 21 | @Constraint(validatedBy = {}) 22 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 23 | @Retention(RUNTIME) 24 | @ReportAsSingleViolation 25 | @NotNull 26 | @DateTimeFormat(iso = ISO.DATE) 27 | @JsonFormat(pattern = "yyyy-MM-dd") 28 | public @interface ISODate { 29 | String message() default "{error.domain.ISODate}"; 30 | 31 | Class[] groups() default {}; 32 | 33 | Class[] payload() default {}; 34 | 35 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 36 | @Retention(RUNTIME) 37 | @Documented 38 | public @interface List { 39 | ISODate[] value(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/ISODateEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | 10 | import org.springframework.format.annotation.DateTimeFormat; 11 | import org.springframework.format.annotation.DateTimeFormat.ISO; 12 | 13 | import com.fasterxml.jackson.annotation.JsonFormat; 14 | 15 | /** 16 | * ISOフォーマットの日付を表現する制約注釈。 17 | *

YYYY-MM-DDを想定します。 18 | */ 19 | @Documented 20 | @Constraint(validatedBy = {}) 21 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 22 | @Retention(RUNTIME) 23 | @ReportAsSingleViolation 24 | @DateTimeFormat(iso = ISO.DATE) 25 | @JsonFormat(pattern = "yyyy-MM-dd") 26 | public @interface ISODateEmpty { 27 | String message() default "{error.domain.ISODate}"; 28 | 29 | Class[] groups() default {}; 30 | 31 | Class[] payload() default {}; 32 | 33 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 34 | @Retention(RUNTIME) 35 | @Documented 36 | public @interface List { 37 | ISODateEmpty[] value(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/ISODateTime.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import org.springframework.format.annotation.DateTimeFormat; 12 | import org.springframework.format.annotation.DateTimeFormat.ISO; 13 | 14 | import com.fasterxml.jackson.annotation.JsonFormat; 15 | 16 | /** 17 | * ISOフォーマットの日時(必須)を表現する制約注釈。 18 | *

yyyy-MM-dd'T'HH:mm:ss.SSSZを想定します。 19 | */ 20 | @Documented 21 | @Constraint(validatedBy = {}) 22 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 23 | @Retention(RUNTIME) 24 | @ReportAsSingleViolation 25 | @NotNull 26 | @DateTimeFormat(iso = ISO.DATE_TIME) 27 | @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") 28 | public @interface ISODateTime { 29 | String message() default "{error.domain.ISODateTime}"; 30 | 31 | Class[] groups() default {}; 32 | 33 | Class[] payload() default {}; 34 | 35 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 36 | @Retention(RUNTIME) 37 | @Documented 38 | public @interface List { 39 | ISODateTime[] value(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/ISODateTimeEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | 10 | import org.springframework.format.annotation.DateTimeFormat; 11 | import org.springframework.format.annotation.DateTimeFormat.ISO; 12 | 13 | import com.fasterxml.jackson.annotation.JsonFormat; 14 | 15 | /** 16 | * ISOフォーマットの日時を表現する制約注釈。 17 | *

yyyy-MM-dd'T'HH:mm:ss.SSSZを想定します。 18 | */ 19 | @Documented 20 | @Constraint(validatedBy = {}) 21 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 22 | @Retention(RUNTIME) 23 | @ReportAsSingleViolation 24 | @DateTimeFormat(iso = ISO.DATE_TIME) 25 | @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") 26 | public @interface ISODateTimeEmpty { 27 | String message() default "{error.domain.ISODateTime}"; 28 | 29 | Class[] groups() default {}; 30 | 31 | Class[] payload() default {}; 32 | 33 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 34 | @Retention(RUNTIME) 35 | @Documented 36 | public @interface List { 37 | ISODateTimeEmpty[] value(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/IdStr.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 文字列ID(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotBlank 20 | @Size 21 | @Pattern(regexp = "") 22 | public @interface IdStr { 23 | String message() default "{error.domain.idStr}"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | @OverridesAttribute(constraint = Size.class, name = "max") 30 | int max() default 32; 31 | 32 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 33 | String regexp() default "^\\p{ASCII}*$"; 34 | 35 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 36 | Pattern.Flag[] flags() default {}; 37 | 38 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 39 | @Retention(RUNTIME) 40 | @Documented 41 | public @interface List { 42 | IdStr[] value(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/IdStrEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 文字列IDを表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Size 20 | @Pattern(regexp = "") 21 | public @interface IdStrEmpty { 22 | String message() default "{error.domain.idStr}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @OverridesAttribute(constraint = Size.class, name = "max") 29 | int max() default 32; 30 | 31 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 32 | String regexp() default "^\\p{ASCII}*$"; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 35 | Pattern.Flag[] flags() default {}; 36 | 37 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 38 | @Retention(RUNTIME) 39 | @Documented 40 | public @interface List { 41 | IdStrEmpty[] value(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Name.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 名称(必須)を表現する制約注釈。 13 | * low: 実際は姓名(ミドルネーム)の考慮やモノ系の名称などを意識する必要があります。 14 | */ 15 | @Documented 16 | @Constraint(validatedBy = {}) 17 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 18 | @Retention(RUNTIME) 19 | @ReportAsSingleViolation 20 | @NotBlank 21 | @Size 22 | @Pattern(regexp = "") 23 | public @interface Name { 24 | String message() default "{error.domain.name}"; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | 30 | @OverridesAttribute(constraint = Size.class, name = "max") 31 | int max() default 30; 32 | 33 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 34 | String regexp() default ".*"; 35 | 36 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 37 | Pattern.Flag[] flags() default {}; 38 | 39 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 40 | @Retention(RUNTIME) 41 | @Documented 42 | public @interface List { 43 | Name[] value(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/NameEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.Pattern; 10 | import javax.validation.constraints.Size; 11 | 12 | /** 13 | * 名称を表現する制約注釈。 14 | */ 15 | @Documented 16 | @Constraint(validatedBy = {}) 17 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 18 | @Retention(RUNTIME) 19 | @ReportAsSingleViolation 20 | @Size 21 | @Pattern(regexp = "") 22 | public @interface NameEmpty { 23 | String message() default "{error.domain.name}"; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | @OverridesAttribute(constraint = Size.class, name = "max") 30 | int max() default 30; 31 | 32 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 33 | String regexp() default ".*"; 34 | 35 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 36 | Pattern.Flag[] flags() default {}; 37 | 38 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 39 | @Retention(RUNTIME) 40 | @Documented 41 | public @interface List { 42 | NameEmpty[] value(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Outline.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import sample.util.Regex; 12 | 13 | /** 14 | * 概要(必須)を表現する制約注釈。 15 | */ 16 | @Documented 17 | @Constraint(validatedBy = {}) 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 19 | @Retention(RUNTIME) 20 | @ReportAsSingleViolation 21 | @NotBlank 22 | @Size 23 | @Pattern(regexp = "") 24 | public @interface Outline { 25 | String message() default "{error.domain.outline}"; 26 | 27 | Class[] groups() default {}; 28 | 29 | Class[] payload() default {}; 30 | 31 | @OverridesAttribute(constraint = Size.class, name = "max") 32 | int max() default 200; 33 | 34 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 35 | String regexp() default Regex.rWord; 36 | 37 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 38 | Pattern.Flag[] flags() default {}; 39 | 40 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 41 | @Retention(RUNTIME) 42 | @Documented 43 | public @interface List { 44 | Outline[] value(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/OutlineEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import sample.util.Regex; 12 | 13 | /** 14 | * 概要を表現する制約注釈。 15 | */ 16 | @Documented 17 | @Constraint(validatedBy = {}) 18 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 19 | @Retention(RUNTIME) 20 | @ReportAsSingleViolation 21 | @Size 22 | @Pattern(regexp = "") 23 | public @interface OutlineEmpty { 24 | String message() default "{error.domain.outline}"; 25 | 26 | Class[] groups() default {}; 27 | 28 | Class[] payload() default {}; 29 | 30 | @OverridesAttribute(constraint = Size.class, name = "max") 31 | int max() default 200; 32 | 33 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 34 | String regexp() default Regex.rWord; 35 | 36 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 37 | Pattern.Flag[] flags() default {}; 38 | 39 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 40 | @Retention(RUNTIME) 41 | @Documented 42 | public @interface List { 43 | OutlineEmpty[] value(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Password.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | import sample.util.Regex; 12 | 13 | /** 14 | * パスワード(必須)を表現する制約注釈。 15 | * low: 実際の定義はプロジェクトに大きく依存するのでサンプルでは適当にしています。 16 | */ 17 | @Documented 18 | @Constraint(validatedBy = {}) 19 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 20 | @Retention(RUNTIME) 21 | @ReportAsSingleViolation 22 | @NotBlank 23 | @Size 24 | @Pattern(regexp = "") 25 | public @interface Password { 26 | String message() default "{error.domain.password}"; 27 | 28 | Class[] groups() default {}; 29 | 30 | Class[] payload() default {}; 31 | 32 | @OverridesAttribute(constraint = Size.class, name = "max") 33 | int max() default 256; 34 | 35 | @OverridesAttribute(constraint = Pattern.class, name = "regexp") 36 | String regexp() default Regex.rAscii; 37 | 38 | @OverridesAttribute(constraint = Pattern.class, name = "flags") 39 | Pattern.Flag[] flags() default {}; 40 | 41 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 42 | @Retention(RUNTIME) 43 | @Documented 44 | public @interface List { 45 | Password[] value(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/Year.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 年(必須)を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @NotNull 20 | @Digits(integer = 4, fraction = 0) 21 | public @interface Year { 22 | String message() default "{error.domain.year}"; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 29 | @Retention(RUNTIME) 30 | @Documented 31 | public @interface List { 32 | Year[] value(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/YearEmpty.java: -------------------------------------------------------------------------------- 1 | package sample.model.constraints; 2 | 3 | import static java.lang.annotation.ElementType.*; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.*; 7 | 8 | import javax.validation.*; 9 | import javax.validation.constraints.*; 10 | 11 | /** 12 | * 年を表現する制約注釈。 13 | */ 14 | @Documented 15 | @Constraint(validatedBy = {}) 16 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 17 | @Retention(RUNTIME) 18 | @ReportAsSingleViolation 19 | @Digits(integer = 4, fraction = 0) 20 | public @interface YearEmpty { 21 | String message() default "{error.domain.year}"; 22 | 23 | Class[] groups() default {}; 24 | 25 | Class[] payload() default {}; 26 | 27 | @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) 28 | @Retention(RUNTIME) 29 | @Documented 30 | public @interface List { 31 | YearEmpty[] value(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/constraints/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ドメインで汎用的に利用される制約定義。 3 | *

JSR303(Bean Valildation)の用途で利用される想定です。 4 | */ 5 | package sample.model.constraints; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/master/SelfFiAccount.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import javax.persistence.*; 4 | 5 | import lombok.*; 6 | import sample.context.orm.*; 7 | import sample.model.constraints.*; 8 | 9 | /** 10 | * サービス事業者の決済金融機関を表現します。 11 | * low: サンプルなので支店や名称、名義といったなど本来必須な情報をかなり省略しています。(通常は全銀仕様を踏襲します) 12 | */ 13 | @Entity 14 | @Data 15 | @EqualsAndHashCode(callSuper = false) 16 | public class SelfFiAccount extends OrmActiveRecord { 17 | private static final long serialVersionUID = 1L; 18 | 19 | /** ID */ 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | /** 利用用途カテゴリ */ 24 | @Category 25 | private String category; 26 | /** 通貨 */ 27 | @Currency 28 | private String currency; 29 | /** 金融機関コード */ 30 | @IdStr 31 | private String fiCode; 32 | /** 金融機関口座ID */ 33 | @IdStr 34 | private String fiAccountId; 35 | 36 | public static SelfFiAccount load(final OrmRepository rep, String category, String currency) { 37 | return rep.tmpl().load("from SelfFiAccount a where a.category=?1 and a.currency=?2", category, currency); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/master/StaffAuthority.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import java.util.List; 4 | 5 | import javax.persistence.*; 6 | 7 | import lombok.*; 8 | import sample.context.orm.*; 9 | import sample.model.constraints.*; 10 | 11 | /** 12 | * 社員に割り当てられた権限を表現します。 13 | */ 14 | @Entity 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @EqualsAndHashCode(callSuper = false) 19 | public class StaffAuthority extends OrmActiveRecord { 20 | private static final long serialVersionUID = 1l; 21 | 22 | /** ID */ 23 | @Id 24 | @GeneratedValue 25 | private Long id; 26 | /** 社員ID */ 27 | @IdStr 28 | private String staffId; 29 | /** 権限名称。(「プリフィックスにROLE_」を付与してください) */ 30 | @Name 31 | private String authority; 32 | 33 | /** 口座IDに紐付く権限一覧を返します。 */ 34 | public static List find(final OrmRepository rep, String staffId) { 35 | return rep.tmpl().find("from StaffAuthority where staffId=?1", staffId); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/master/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * マスタに関連したドメイン概念。 3 | */ 4 | package sample.model.master; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/model/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ドメイン層のコンポーネントやEntity、クラスなどを管理。 3 | *

ドメイン層のコンポーネントがUI層やアプリケーション層を呼び出す事は想定していません。 4 | */ 5 | package sample.model; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーションルートディレクトリ。 3 | *

Applicationクラスを実行する事でアプリケーションプロセスが立ち上がります。 4 | */ 5 | package sample; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/usecase/ServiceUtils.java: -------------------------------------------------------------------------------- 1 | package sample.usecase; 2 | 3 | import sample.ValidationException; 4 | import sample.ValidationException.ErrorKeys; 5 | import sample.context.actor.Actor; 6 | 7 | /** 8 | * Serviceで利用されるユーティリティ処理。 9 | */ 10 | public abstract class ServiceUtils { 11 | 12 | /** 匿名以外の利用者情報を返します。 */ 13 | public static Actor actorUser(Actor actor) { 14 | if (actor.getRoleType().isAnonymous()) { 15 | throw new ValidationException(ErrorKeys.Authentication); 16 | } 17 | return actor; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/usecase/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーション層のコンポーネント。 3 | *

UI層から呼び出され、ドメイン層のコンポーネントを利用してユースケース処理を実現します。 4 | * アプリケーション層のコンポーネントがUI層を呼び出す事やService同士で相互依存することは想定していません。 5 | */ 6 | package sample.usecase; -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/util/Checker.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | /** 4 | * 簡易的な入力チェッカーを表現します。 5 | */ 6 | public abstract class Checker { 7 | 8 | /** 9 | * 正規表現に文字列がマッチするか。(nullは許容) 10 | *

引数のregexにはRegex定数を利用する事を推奨します。 11 | */ 12 | public static boolean match(String regex, Object v) { 13 | return v != null ? v.toString().matches(regex) : true; 14 | } 15 | 16 | /** 文字桁数チェック、max以下の時はtrue。(サロゲートペア対応) */ 17 | public static boolean len(String v, int max) { 18 | return wordSize(v) <= max; 19 | } 20 | 21 | private static int wordSize(String v) { 22 | return v.codePointCount(0, v.length()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/util/Regex.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | /** 4 | * 正規表現定数インターフェース。 5 | *

Checker.matchと組み合わせて利用してください。 6 | */ 7 | public interface Regex { 8 | /** Ascii */ 9 | String rAscii = "^\\p{ASCII}*$"; 10 | /** 英字 */ 11 | String rAlpha = "^[a-zA-Z]*$"; 12 | /** 英字大文字 */ 13 | String rAlphaUpper = "^[A-Z]*$"; 14 | /** 英字小文字 */ 15 | String rAlphaLower = "^[a-z]*$"; 16 | /** 英数 */ 17 | String rAlnum = "^[0-9a-zA-Z]*$"; 18 | /** シンボル */ 19 | String rSymbol = "^\\p{Punct}*$"; 20 | /** 英数記号 */ 21 | String rAlnumSymbol = "^[0-9a-zA-Z\\p{Punct}]*$"; 22 | /** 数字 */ 23 | String rNumber = "^[-]?[0-9]*$"; 24 | /** 整数 */ 25 | String rNumberNatural = "^[0-9]*$"; 26 | /** 倍精度浮動小数点 */ 27 | String rDecimal = "^[-]?(\\d+)(\\.\\d+)?$"; 28 | // see UnicodeBlock 29 | /** ひらがな */ 30 | String rHiragana = "^\\p{InHiragana}*$"; 31 | /** カタカナ */ 32 | String rKatakana = "^\\p{InKatakana}*$"; 33 | /** 半角カタカナ */ 34 | String rHankata = "^[。-゚]*$"; 35 | /** 半角文字列 */ 36 | String rHankaku = "^[\\p{InBasicLatin}。-゚]*$"; // ラテン文字 + 半角カタカナ 37 | /** 全角文字列 */ 38 | String rZenkaku = "^[^\\p{InBasicLatin}。-゚]*$"; // 全角の定義を半角以外で割り切り 39 | /** 漢字 */ 40 | String rKanji = "^[\\p{InCJKUnifiedIdeographs}々\\p{InCJKCompatibilityIdeographs}]*$"; 41 | /** 文字 */ 42 | String rWord = "^(?s).*$"; 43 | /** コード */ 44 | String rCode = "^[0-9a-zA-Z_-]*$"; // 英数 + アンダーバー + ハイフン 45 | } 46 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/util/TimePoint.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import java.io.Serializable; 4 | import java.time.*; 5 | 6 | import lombok.Value; 7 | import sample.model.constraints.*; 8 | 9 | /** 10 | * 日付と日時のペアを表現します。 11 | *

0:00に営業日切り替えが行われないケースなどでの利用を想定しています。 12 | */ 13 | @Value 14 | public class TimePoint implements Serializable { 15 | private static final long serialVersionUID = 1L; 16 | /** 日付(営業日) */ 17 | @ISODate 18 | private LocalDate day; 19 | /** 日付におけるシステム日時 */ 20 | @ISODateTime 21 | private LocalDateTime date; 22 | 23 | public LocalDate day() { 24 | return getDay(); 25 | } 26 | 27 | public LocalDateTime date() { 28 | return getDate(); 29 | } 30 | 31 | /** 指定日付と同じか。(day == targetDay) */ 32 | public boolean equalsDay(LocalDate targetDay) { 33 | return day.compareTo(targetDay) == 0; 34 | } 35 | 36 | /** 指定日付よりも前か。(day < targetDay) */ 37 | public boolean beforeDay(LocalDate targetDay) { 38 | return day.compareTo(targetDay) < 0; 39 | } 40 | 41 | /** 指定日付以前か。(day <= targetDay) */ 42 | public boolean beforeEqualsDay(LocalDate targetDay) { 43 | return day.compareTo(targetDay) <= 0; 44 | } 45 | 46 | /** 指定日付よりも後か。(targetDay < day) */ 47 | public boolean afterDay(LocalDate targetDay) { 48 | return 0 < day.compareTo(targetDay); 49 | } 50 | 51 | /** 指定日付以降か。(targetDay <= day) */ 52 | public boolean afterEqualsDay(LocalDate targetDay) { 53 | return 0 <= day.compareTo(targetDay); 54 | } 55 | 56 | /** 日付/日時を元にTimePointを生成します。 */ 57 | public static TimePoint of(LocalDate day, LocalDateTime date) { 58 | return new TimePoint(day, date); 59 | } 60 | 61 | /** 日付を元にTimePointを生成します。 */ 62 | public static TimePoint of(LocalDate day) { 63 | return of(day, day.atStartOfDay()); 64 | } 65 | 66 | /** TimePointを生成します。 */ 67 | public static TimePoint now() { 68 | LocalDateTime now = LocalDateTime.now(); 69 | return of(now.toLocalDate(), now); 70 | } 71 | 72 | /** TimePointを生成します。 */ 73 | public static TimePoint now(Clock clock) { 74 | LocalDateTime now = LocalDateTime.now(clock); 75 | return of(now.toLocalDate(), now); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/util/Validator.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import sample.*; 6 | import sample.ValidationException.Warns; 7 | 8 | /** 9 | * 審査例外の構築概念を表現します。 10 | */ 11 | public class Validator { 12 | private Warns warns = Warns.init(); 13 | 14 | /** 審査処理を行います。 */ 15 | public static void validate(Consumer proc) { 16 | Validator validator = new Validator(); 17 | proc.accept(validator); 18 | validator.verify(); 19 | } 20 | 21 | /** 審査を行います。validがfalseの時に例外を内部にスタックします。 */ 22 | public Validator check(boolean valid, String message) { 23 | if (!valid) 24 | warns.add(message); 25 | return this; 26 | } 27 | 28 | /** 個別属性の審査を行います。validがfalseの時に例外を内部にスタックします。 */ 29 | public Validator checkField(boolean valid, String field, String message) { 30 | if (!valid) 31 | warns.add(field, message); 32 | return this; 33 | } 34 | 35 | /** 審査を行います。失敗した時は即時に例外を発生させます。 */ 36 | public Validator verify(boolean valid, String message) { 37 | return check(valid, message).verify(); 38 | } 39 | 40 | /** 個別属性の審査を行います。失敗した時は即時に例外を発生させます。 */ 41 | public Validator verifyField(boolean valid, String field, String message) { 42 | return checkField(valid, field, message).verify(); 43 | } 44 | 45 | /** 検証します。事前に行ったcheckで例外が存在していた時は例外を発生させます。 */ 46 | public Validator verify() { 47 | if (hasWarn()) 48 | throw new ValidationException(warns); 49 | return clear(); 50 | } 51 | 52 | /** 審査例外を保有している時はtrueを返します。 */ 53 | public boolean hasWarn() { 54 | return warns.nonEmpty(); 55 | } 56 | 57 | /** 内部に保有する審査例外を初期化します。 */ 58 | public Validator clear() { 59 | warns.list().clear(); 60 | return this; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /micro-core/src/main/java/sample/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 汎用的なユーティリティクラス。 3 | */ 4 | package sample.util; -------------------------------------------------------------------------------- /micro-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application.name: sample-boot-micro 4 | messages.basename: messages-validation, messages 5 | banner.location: banner.txt 6 | cache.jcache.config: classpath:ehcache.xml 7 | jackson.serialization: 8 | indent-output: true 9 | servlet.multipart: 10 | max-file-size: 20MB 11 | max-request-size: 20MB 12 | jpa.open-in-view: false 13 | cloud.loadbalancer.ribbon.enabled: false 14 | 15 | logging.config: classpath:logback-spring.xml 16 | 17 | server: 18 | port: 8080 19 | error: 20 | whitelabel.enabled: false 21 | path: /api/error 22 | 23 | management: 24 | endpoints.web: 25 | base-path: /management 26 | exposure.include: "*" 27 | endpoint: 28 | health: 29 | show-details: ALWAYS 30 | 31 | eureka: 32 | instance: 33 | status-page-url-path: ${management.endpoints.web.base-path}/info 34 | health-check-url-path: ${management.endpoints.web.base-path}/health 35 | client: 36 | enabled: true 37 | register-with-eureka: true 38 | fetch-registry: true 39 | service-url: 40 | defaultZone: ${EUREKA_URL:http://localhost:8761/eureka/} 41 | 42 | extension: 43 | security.auth.enabled: false 44 | datasource: 45 | default: 46 | url: ${DB_DEFAULT_JDBC_URL:jdbc:h2:tcp://localhost:9092/mem:testdb} 47 | username: ${DB_DEFAULT_JDBC_USERNAME:sa} 48 | password: ${DB_DEFAULT_JDBC_PASSWORD:} 49 | jpa: 50 | package-to-scan: sample.model 51 | show-sql: false 52 | hibernate.ddl-auto: none 53 | system: 54 | url: ${DB_SYSTEM_JDBC_URL:jdbc:h2:tcp://localhost:9092/mem:system} 55 | username: ${DB_SYSTEM_JDBC_USERNAME:sa} 56 | password: ${DB_SYSTEM_JDBC_PASSWORD:} 57 | jpa: 58 | package-to-scan: sample.context 59 | show-sql: false 60 | hibernate.ddl-auto: none 61 | -------------------------------------------------------------------------------- /micro-core/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ====================================================================================== 2 | Start Application [ sample-boot-micro ] 3 | -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /micro-core/src/main/resources/ehcache.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 1800 17 | 18 | 19 | 1000 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 29 | 30 | 1000 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 100 45 | 46 | 47 | 10000 48 | 49 | 50 | -------------------------------------------------------------------------------- /micro-core/src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /micro-core/src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | # サービスで利用されるラベルメッセージファイルです。 2 | 3 | # -- 定数 [Remarks] 4 | Remarks.cashIn=振込入金 5 | Remarks.cashInAdjust=振込入金(調整) 6 | Remarks.cashInCancel=振込入金(取消) 7 | Remarks.cashOut=振込出金 8 | Remarks.cashOutAdjust=振込出金(調整) 9 | Remarks.cashOutCancel=振込出金(取消) 10 | 11 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/context/ResourceBundleHandlerTest.java: -------------------------------------------------------------------------------- 1 | package sample.context; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import org.junit.Test; 6 | 7 | public class ResourceBundleHandlerTest { 8 | 9 | @Test 10 | public void ラベル取得検証() { 11 | ResourceBundleHandler handler = new ResourceBundleHandler(); 12 | assertTrue(0 < handler.labels("messages").size()); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/account/AccountTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.account; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import sample.*; 9 | import sample.ValidationException.ErrorKeys; 10 | import sample.model.account.Account.*; 11 | import sample.model.account.type.AccountStatusType; 12 | 13 | public class AccountTest extends EntityTestSupport { 14 | 15 | @Override 16 | protected void setupPreset() { 17 | targetEntities(Account.class, Login.class); 18 | } 19 | 20 | @Override 21 | protected void before() { 22 | tx(() -> fixtures.acc("normal").save(rep)); 23 | } 24 | 25 | @Test 26 | public void 口座情報を登録する() { 27 | tx(() -> { 28 | // 通常登録 29 | assertFalse(Account.get(rep, "new").isPresent()); 30 | Account.register(rep, encoder, new RegAccount("new", "name", "new@example.com", "password")); 31 | assertThat(Account.load(rep, "new"), allOf( 32 | hasProperty("name", is("name")), 33 | hasProperty("mail", is("new@example.com")))); 34 | Login login = Login.load(rep, "new"); 35 | assertTrue(encoder.matches("password", login.getPassword())); 36 | 37 | // 同一ID重複 38 | try { 39 | Account.register(rep, encoder, new RegAccount("normal", "name", "new@example.com", "password")); 40 | fail(); 41 | } catch (ValidationException e) { 42 | assertThat(e.getMessage(), is(ErrorKeys.DuplicateId)); 43 | } 44 | }); 45 | } 46 | 47 | @Test 48 | public void 口座情報を変更する() { 49 | tx(() -> { 50 | Account.load(rep, "normal").change(rep, new ChgAccount("changed", "changed@example.com")); 51 | assertThat(Account.load(rep, "normal"), allOf( 52 | hasProperty("name", is("changed")), 53 | hasProperty("mail", is("changed@example.com")))); 54 | }); 55 | } 56 | 57 | @Test 58 | public void 有効口座を取得する() { 59 | tx(() -> { 60 | // 通常時取得 61 | assertThat(Account.loadValid(rep, "normal"), allOf( 62 | hasProperty("id", is("normal")), 63 | hasProperty("statusType", is(AccountStatusType.Normal)))); 64 | 65 | // 退会時取得 66 | Account withdrawal = fixtures.acc("withdrawal"); 67 | withdrawal.setStatusType(AccountStatusType.Withdrawal); 68 | withdrawal.save(rep); 69 | try { 70 | Account.loadValid(rep, "withdrawal"); 71 | fail(); 72 | } catch (ValidationException e) { 73 | assertThat(e.getMessage(), is("error.Account.loadValid")); 74 | } 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/account/FiAccountTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.account; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import lombok.Value; 9 | import sample.*; 10 | import sample.ValidationException.ErrorKeys; 11 | 12 | public class FiAccountTest extends EntityTestSupport { 13 | 14 | @Override 15 | protected void setupPreset() { 16 | targetEntities(FiAccount.class, Account.class); 17 | } 18 | 19 | @Override 20 | protected void before() { 21 | tx(() -> fixtures.fiAcc("normal", "sample", "JPY").save(rep)); 22 | } 23 | 24 | @Test 25 | public void 金融機関口座を取得する() { 26 | tx(() -> { 27 | assertThat(FiAccount.load(rep, "normal", "sample", "JPY"), allOf( 28 | hasProperty("accountId", is("normal")), 29 | hasProperty("category", is("sample")), 30 | hasProperty("currency", is("JPY")), 31 | hasProperty("fiCode", is("sample-JPY")), 32 | hasProperty("fiAccountId", is("FInormal")))); 33 | try { 34 | FiAccount.load(rep, "normal", "sample", "USD"); 35 | fail(); 36 | } catch (ValidationException e) { 37 | assertThat(e.getMessage(), is(ErrorKeys.EntityNotFound)); 38 | } 39 | }); 40 | } 41 | 42 | @Value 43 | private static class FiAccountJoin { 44 | private String accountId; 45 | private String name; 46 | private String fiCode; 47 | private String fiAcountId; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/account/LoginTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.account; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import sample.*; 9 | import sample.ValidationException.ErrorKeys; 10 | import sample.model.account.Login.*; 11 | 12 | public class LoginTest extends EntityTestSupport { 13 | @Override 14 | protected void setupPreset() { 15 | targetEntities(Login.class); 16 | } 17 | 18 | @Override 19 | protected void before() { 20 | tx(() -> fixtures.login("test").save(rep)); 21 | } 22 | 23 | @Test 24 | public void ログインIDを変更する() { 25 | tx(() -> { 26 | // 正常系 27 | fixtures.login("any").save(rep); 28 | assertThat(Login.load(rep, "any").change(rep, new ChgLoginId("testAny")), allOf( 29 | hasProperty("id", is("any")), 30 | hasProperty("loginId", is("testAny")))); 31 | 32 | // 自身に対する同名変更 33 | assertThat(Login.load(rep, "any").change(rep, new ChgLoginId("testAny")), allOf( 34 | hasProperty("id", is("any")), 35 | hasProperty("loginId", is("testAny")))); 36 | 37 | // 重複ID 38 | try { 39 | Login.load(rep, "any").change(rep, new ChgLoginId("test")); 40 | fail(); 41 | } catch (ValidationException e) { 42 | assertThat(e.getMessage(), is(ErrorKeys.DuplicateId)); 43 | } 44 | }); 45 | } 46 | 47 | @Test 48 | public void パスワードを変更する() { 49 | tx(() -> { 50 | Login login = Login.load(rep, "test").change(rep, encoder, new ChgPassword("changed")); 51 | assertTrue(encoder.matches("changed", login.getPassword())); 52 | }); 53 | } 54 | 55 | @Test 56 | public void ログイン情報を取得する() { 57 | tx(() -> { 58 | Login m = Login.load(rep, "test"); 59 | m.setLoginId("changed"); 60 | m.update(rep); 61 | assertTrue(Login.getByLoginId(rep, "changed").isPresent()); 62 | assertFalse(Login.getByLoginId(rep, "test").isPresent()); 63 | }); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/master/HolidayTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.time.LocalDate; 7 | import java.util.*; 8 | import java.util.stream.Collectors; 9 | 10 | import org.junit.Test; 11 | 12 | import sample.EntityTestSupport; 13 | import sample.model.master.Holiday.*; 14 | import sample.util.DateUtils; 15 | 16 | public class HolidayTest extends EntityTestSupport { 17 | 18 | @Override 19 | protected void setupPreset() { 20 | targetEntities(Holiday.class); 21 | } 22 | 23 | @Override 24 | protected void before() { 25 | tx(() -> { 26 | Arrays.asList("2015-09-21", "2015-09-22", "2015-09-23", "2016-09-21") 27 | .stream().map(fixtures::holiday).forEach((m) -> m.save(rep)); 28 | }); 29 | } 30 | 31 | @Test 32 | public void 休日を取得する() { 33 | tx(() -> { 34 | Optional day = Holiday.get(rep, LocalDate.of(2015, 9, 22)); 35 | assertTrue(day.isPresent()); 36 | assertThat(day.get().getDay(), is(LocalDate.of(2015, 9, 22))); 37 | }); 38 | } 39 | 40 | @Test 41 | public void 休日を検索する() { 42 | tx(() -> { 43 | assertThat(Holiday.find(rep, 2015), hasSize(3)); 44 | assertThat(Holiday.find(rep, 2016), hasSize(1)); 45 | }); 46 | } 47 | 48 | @Test 49 | public void 休日を登録する() { 50 | List items = Arrays.asList("2016-09-21", "2016-09-22", "2016-09-23") 51 | .stream().map((s) -> new RegHolidayItem(DateUtils.day(s), "休日")).collect(Collectors.toList()); 52 | tx(() -> { 53 | Holiday.register(rep, new RegHoliday(2016, items)); 54 | assertThat(Holiday.find(rep, 2016), hasSize(3)); 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/master/SelfFiAccountTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import sample.*; 9 | import sample.ValidationException.ErrorKeys; 10 | 11 | public class SelfFiAccountTest extends EntityTestSupport { 12 | 13 | @Override 14 | protected void setupPreset() { 15 | targetEntities(SelfFiAccount.class); 16 | } 17 | 18 | @Override 19 | protected void before() { 20 | tx(() -> fixtures.selfFiAcc("sample", "JPY").save(rep)); 21 | } 22 | 23 | @Test 24 | public void 自社金融機関口座を取得する() { 25 | tx(() -> { 26 | assertThat(SelfFiAccount.load(rep, "sample", "JPY"), allOf( 27 | hasProperty("category", is("sample")), 28 | hasProperty("currency", is("JPY")), 29 | hasProperty("fiCode", is("sample-JPY")), 30 | hasProperty("fiAccountId", is("xxxxxx")))); 31 | try { 32 | SelfFiAccount.load(rep, "sample", "USD"); 33 | fail(); 34 | } catch (ValidationException e) { 35 | assertThat(e.getMessage(), is(ErrorKeys.EntityNotFound)); 36 | } 37 | }); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/master/StaffAuthorityTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import sample.EntityTestSupport; 9 | 10 | public class StaffAuthorityTest extends EntityTestSupport { 11 | 12 | @Override 13 | protected void setupPreset() { 14 | targetEntities(StaffAuthority.class); 15 | } 16 | 17 | @Override 18 | protected void before() { 19 | tx(() -> { 20 | fixtures.staffAuth("staffA", "ID000001", "ID000002", "ID000003").forEach((auth) -> auth.save(rep)); 21 | fixtures.staffAuth("staffB", "ID000001", "ID000002").forEach((auth) -> auth.save(rep)); 22 | }); 23 | } 24 | 25 | @Test 26 | public void 権限一覧を検索する() { 27 | tx(() -> { 28 | assertThat(StaffAuthority.find(rep, "staffA").size(), is(3)); 29 | assertThat(StaffAuthority.find(rep, "staffB").size(), is(2)); 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/model/master/StaffTest.java: -------------------------------------------------------------------------------- 1 | package sample.model.master; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import org.junit.Test; 7 | 8 | import sample.*; 9 | import sample.ValidationException.ErrorKeys; 10 | import sample.model.master.Staff.*; 11 | 12 | public class StaffTest extends EntityTestSupport { 13 | 14 | @Override 15 | protected void setupPreset() { 16 | targetEntities(Staff.class); 17 | } 18 | 19 | @Override 20 | protected void before() { 21 | tx(() -> fixtures.staff("sample").save(rep)); 22 | } 23 | 24 | @Test 25 | public void 社員情報を登録する() { 26 | tx(() -> { 27 | // 正常登録 28 | Staff staff = Staff.register(rep, encoder, new RegStaff("new", "newName", "password")); 29 | assertThat(staff, allOf( 30 | hasProperty("id", is("new")), 31 | hasProperty("name", is("newName")))); 32 | assertTrue(encoder.matches("password", staff.getPassword())); 33 | 34 | // 重複ID 35 | try { 36 | Staff.register(rep, encoder, new RegStaff("sample", "newName", "password")); 37 | fail(); 38 | } catch (ValidationException e) { 39 | assertThat(e.getMessage(), is(ErrorKeys.DuplicateId)); 40 | } 41 | }); 42 | } 43 | 44 | @Test 45 | public void 社員パスワードを変更する() { 46 | tx(() -> { 47 | Staff changed = Staff.load(rep, "sample").change(rep, encoder, new ChgPassword("changed")); 48 | assertTrue(encoder.matches("changed", changed.getPassword())); 49 | }); 50 | } 51 | 52 | @Test 53 | public void 社員情報を変更する() { 54 | tx(() -> { 55 | assertThat( 56 | Staff.load(rep, "sample").change(rep, new ChgStaff("changed")).getName(), is("changed")); 57 | }); 58 | } 59 | 60 | @Test 61 | public void 社員を検索する() { 62 | tx(() -> { 63 | assertFalse(Staff.find(rep, new FindStaff("amp")).isEmpty()); 64 | assertTrue(Staff.find(rep, new FindStaff("amq")).isEmpty()); 65 | }); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/support/MockDomainHelper.java: -------------------------------------------------------------------------------- 1 | package sample.support; 2 | 3 | import java.time.Clock; 4 | import java.util.*; 5 | 6 | import sample.context.*; 7 | import sample.context.actor.ActorSession; 8 | 9 | /** モックテスト用のドメインヘルパー */ 10 | public class MockDomainHelper extends DomainHelper { 11 | 12 | private Map settingMap = new HashMap<>(); 13 | 14 | public MockDomainHelper() { 15 | this(Clock.systemDefaultZone()); 16 | } 17 | 18 | public MockDomainHelper(final Clock mockClock) { 19 | setActorSession(new ActorSession()); 20 | setTime(new Timestamper(mockClock)); 21 | setSettingHandler(new AppSettingHandler(settingMap)); 22 | } 23 | 24 | public MockDomainHelper setting(String id, String value) { 25 | settingMap.put(id, value); 26 | return this; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/CalculatorTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.math.*; 7 | 8 | import org.junit.Test; 9 | 10 | public class CalculatorTest { 11 | 12 | @Test 13 | public void 単純な四則演算検証() { 14 | 15 | // (10 + 2 - 4) * 4 / 8 = 4 16 | assertThat( 17 | Calculator.of(10).add(2).subtract(4).multiply(4).divideBy(8).intValue(), 18 | is(4)); 19 | 20 | // (12.4 + 0.033 - 2.33) * 0.3 / 3.3 = 0.91 (RoundingMode.DOWN) 21 | assertThat( 22 | Calculator.of(12.4).scale(2).add(0.033).subtract(2.33).multiply(0.3).divideBy(3.3).decimal(), 23 | is(new BigDecimal("0.91"))); 24 | } 25 | 26 | @Test 27 | public void 累積端数処理の検証() { 28 | 29 | // 3.333 -> 3.334 -> 3.335 (= 3.34) 30 | assertThat( 31 | Calculator.of(3.333).scale(2, RoundingMode.HALF_UP) 32 | .add(0.001).add(0.001).decimal(), 33 | is(new BigDecimal("3.34"))); 34 | 35 | // 3.333 -> 3.330 -> 3.330 (= 3.33) 36 | assertThat( 37 | Calculator.of(3.333).scale(2, RoundingMode.HALF_UP).roundingAlways(true) 38 | .add(0.001).add(0.001).decimal(), 39 | is(new BigDecimal("3.33"))); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/CheckerTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | public class CheckerTest { 8 | 9 | @Test 10 | public void 正規表現チェック() { 11 | assertTrue(Checker.match(Regex.rAlnum, "19azAZ")); 12 | assertFalse(Checker.match(Regex.rAlnum, "19azAZ-")); 13 | assertTrue(Checker.match(Regex.rKanji, "漢字")); 14 | assertFalse(Checker.match(Regex.rAlnum, "漢字ひらがな")); 15 | } 16 | 17 | @Test 18 | public void 桁数チェック() { 19 | assertTrue(Checker.len("テスト文字列", 6)); 20 | assertFalse(Checker.len("テスト文字列超", 6)); 21 | 22 | // サロゲートペアチェック 23 | assertTrue("テスト文字𩸽".length() == 7); 24 | assertTrue(Checker.len("テスト文字𩸽", 6)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/ConvertUtilsTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import org.junit.Test; 9 | 10 | public class ConvertUtilsTest { 11 | 12 | @Test 13 | public void 例外無視変換() { 14 | assertThat(ConvertUtils.quietlyLong("8"), is(8L)); 15 | assertNull(ConvertUtils.quietlyLong("a")); 16 | assertThat(ConvertUtils.quietlyInt("8"), is(8)); 17 | assertNull(ConvertUtils.quietlyInt("a")); 18 | assertThat(ConvertUtils.quietlyDecimal("8.3"), is(new BigDecimal("8.3"))); 19 | assertNull(ConvertUtils.quietlyDecimal("a")); 20 | assertTrue(ConvertUtils.quietlyBool("true")); 21 | assertFalse(ConvertUtils.quietlyBool("a")); 22 | } 23 | 24 | @Test 25 | public void 文字列変換() { 26 | assertThat(ConvertUtils.zenkakuToHan("aA19aA19あアア"), is("aA19aA19あアア")); 27 | assertThat(ConvertUtils.hankakuToZen("aA19aA19あアア"), is("aA19aA19あアア")); 28 | assertThat(ConvertUtils.katakanaToHira("aA19aA19あアア"), is("aA19aA19あああ")); 29 | assertThat(ConvertUtils.hiraganaToZenKana("aA19aA19あアア"), is("aA19aA19アアア")); 30 | assertThat(ConvertUtils.hiraganaToHanKana("aA19aA19あアア"), is("aA19aA19アアア")); 31 | } 32 | 33 | @Test 34 | public void 桁数操作及びサロゲートペア対応() { 35 | assertThat(ConvertUtils.substring("あ𠮷い", 0, 3), is("あ𠮷い")); 36 | assertThat(ConvertUtils.substring("あ𠮷い", 1, 2), is("𠮷")); 37 | assertThat(ConvertUtils.substring("あ𠮷い", 1, 3), is("𠮷い")); 38 | assertThat(ConvertUtils.substring("あ𠮷い", 2, 3), is("い")); 39 | assertThat(ConvertUtils.left("あ𠮷い", 2), is("あ𠮷")); 40 | assertThat(ConvertUtils.leftStrict("あ𠮷い", 6, "UTF-8"), is("あ𠮷")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/DateUtilsTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.time.*; 7 | import java.time.format.DateTimeFormatter; 8 | import java.util.Optional; 9 | 10 | import org.junit.Test; 11 | 12 | public class DateUtilsTest { 13 | private final LocalDateTime targetDate = LocalDateTime.of(2015, 8, 29, 1, 23, 31); 14 | private final LocalDate targetDay = LocalDate.of(2015, 8, 29); 15 | 16 | @Test 17 | public void 初期化検証() { 18 | assertThat(DateUtils.day("2015-08-29"), is(targetDay)); 19 | assertThat( 20 | DateUtils.date("2015-08-29T01:23:31", DateTimeFormatter.ISO_LOCAL_DATE_TIME), 21 | is(targetDate)); 22 | assertThat( 23 | DateUtils.dateOpt("2015-08-29T01:23:31", DateTimeFormatter.ISO_LOCAL_DATE_TIME), 24 | is(Optional.of(targetDate))); 25 | assertThat(DateUtils.dateOpt(null, DateTimeFormatter.ISO_LOCAL_DATE_TIME), is(Optional.empty())); 26 | assertThat(DateUtils.date("20150829012331", "yyyyMMddHHmmss"), is(targetDate)); 27 | 28 | assertThat(DateUtils.dateByDay(targetDay), is(LocalDateTime.of(2015, 8, 29, 0, 0, 0))); 29 | assertThat(DateUtils.dateTo(targetDay), is(LocalDateTime.of(2015, 8, 29, 23, 59, 59))); 30 | } 31 | 32 | @Test 33 | public void フォーマット検証() { 34 | assertThat( 35 | DateUtils.dateFormat(targetDate, DateTimeFormatter.ISO_LOCAL_TIME), is("01:23:31")); 36 | assertThat(DateUtils.dateFormat(targetDate, "MM/dd HH:mm"), is("08/29 01:23")); 37 | } 38 | 39 | @Test 40 | public void サポートユーティリティ検証() { 41 | LocalDate startDay = LocalDate.of(2015, 8, 1); 42 | LocalDate endDay = LocalDate.of(2015, 8, 31); 43 | assertTrue(DateUtils.between(startDay, endDay).isPresent()); 44 | assertFalse(DateUtils.between(startDay, null).isPresent()); 45 | assertFalse(DateUtils.between(null, endDay).isPresent()); 46 | assertThat(DateUtils.between(startDay, endDay).get().getDays(), is(30)); // 31でない点に注意 47 | 48 | LocalDateTime startDate = LocalDateTime.of(2015, 8, 1, 01, 23, 31); 49 | LocalDateTime endDate = LocalDateTime.of(2015, 8, 31, 00, 23, 31); 50 | assertTrue(DateUtils.between(startDate, endDate).isPresent()); 51 | assertFalse(DateUtils.between(startDate, null).isPresent()); 52 | assertFalse(DateUtils.between(null, endDate).isPresent()); 53 | assertThat(DateUtils.between(startDate, endDate).get().toDays(), is(29L)); // 30でない点に注意 54 | 55 | assertTrue(DateUtils.isWeekend(LocalDate.of(2015, 8, 29))); 56 | assertFalse(DateUtils.isWeekend(LocalDate.of(2015, 8, 28))); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/TimePointTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.time.*; 7 | 8 | import org.junit.Test; 9 | 10 | public class TimePointTest { 11 | 12 | @Test 13 | public void 初期化検証() { 14 | LocalDate targetDay = LocalDate.of(2015, 8, 28); 15 | LocalDateTime targetDate = LocalDateTime.of(2015, 8, 29, 1, 23, 31); 16 | 17 | TimePoint tp = TimePoint.of(targetDay, targetDate); 18 | assertThat(tp, allOf( 19 | hasProperty("day", is(targetDay)), 20 | hasProperty("date", is(targetDate)))); 21 | 22 | TimePoint tpDay = TimePoint.of(targetDay); 23 | assertThat(tpDay, allOf( 24 | hasProperty("day", is(targetDay)), 25 | hasProperty("date", is(targetDay.atStartOfDay())))); 26 | 27 | TimePoint now = TimePoint.now(); 28 | assertThat(now, allOf( 29 | hasProperty("day", not(nullValue())), 30 | hasProperty("date", not(nullValue())))); 31 | } 32 | 33 | @Test 34 | public void 比較検証() { 35 | LocalDate targetDay = LocalDate.of(2015, 8, 28); 36 | LocalDateTime targetDate = LocalDateTime.of(2015, 8, 29, 1, 23, 31); 37 | 38 | TimePoint tp = TimePoint.of(targetDay, targetDate); 39 | assertTrue(tp.equalsDay(LocalDate.of(2015, 8, 28))); 40 | assertFalse(tp.equalsDay(LocalDate.of(2015, 8, 27))); 41 | assertFalse(tp.equalsDay(LocalDate.of(2015, 8, 29))); 42 | 43 | assertTrue(tp.beforeDay(LocalDate.of(2015, 8, 29))); 44 | assertFalse(tp.beforeDay(LocalDate.of(2015, 8, 28))); 45 | assertFalse(tp.beforeDay(LocalDate.of(2015, 8, 27))); 46 | 47 | assertTrue(tp.afterDay(LocalDate.of(2015, 8, 27))); 48 | assertFalse(tp.afterDay(LocalDate.of(2015, 8, 28))); 49 | assertFalse(tp.afterDay(LocalDate.of(2015, 8, 29))); 50 | 51 | assertTrue(tp.beforeEqualsDay(LocalDate.of(2015, 8, 29))); 52 | assertTrue(tp.beforeEqualsDay(LocalDate.of(2015, 8, 28))); 53 | assertFalse(tp.beforeEqualsDay(LocalDate.of(2015, 8, 27))); 54 | 55 | assertTrue(tp.afterEqualsDay(LocalDate.of(2015, 8, 27))); 56 | assertTrue(tp.afterEqualsDay(LocalDate.of(2015, 8, 28))); 57 | assertFalse(tp.afterEqualsDay(LocalDate.of(2015, 8, 29))); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /micro-core/src/test/java/sample/util/ValidatorTest.java: -------------------------------------------------------------------------------- 1 | package sample.util; 2 | 3 | import static org.hamcrest.Matchers.*; 4 | import static org.junit.Assert.*; 5 | 6 | import java.util.List; 7 | 8 | import org.junit.Test; 9 | 10 | import sample.ValidationException; 11 | import sample.ValidationException.Warn; 12 | 13 | public class ValidatorTest { 14 | 15 | @Test 16 | public void ラムダ式ベースの検証() { 17 | Validator.validate((v) -> { 18 | boolean anyCheck = true; 19 | v.checkField(anyCheck, "name", "error.name"); 20 | }); 21 | 22 | // フィールドレベルのチェック 23 | try { 24 | Validator.validate((v) -> { 25 | boolean anyCheck = false; 26 | v.checkField(anyCheck, "name", "error.name"); 27 | v.checkField(anyCheck, "day", "error.day"); 28 | v.checkField(true, "description", "error.description"); 29 | }); 30 | fail(); 31 | } catch (ValidationException e) { 32 | List warns = e.list(); 33 | assertThat(warns.size(), is(2)); 34 | assertThat(warns.get(0).getField(), is("name")); 35 | assertThat(warns.get(0).getMessage(), is("error.name")); 36 | assertThat(warns.get(1).getField(), is("day")); 37 | assertThat(warns.get(1).getMessage(), is("error.day")); 38 | } 39 | 40 | // グローバルチェック 41 | try { 42 | Validator.validate((v) -> { 43 | boolean anyCheck = false; 44 | v.check(anyCheck, "error.global"); 45 | }); 46 | fail(); 47 | } catch (ValidationException e) { 48 | List warns = e.list(); 49 | assertThat(warns.size(), is(1)); 50 | assertNull(warns.get(0).getField()); 51 | assertThat(warns.get(0).getMessage(), is("error.global")); 52 | } 53 | } 54 | 55 | @Test 56 | public void 手続きベースの検証() { 57 | Validator v = new Validator(); 58 | boolean anyCheck = false; 59 | v.checkField(anyCheck, "name", "error.name"); 60 | try { 61 | v.verify(); 62 | fail(); 63 | } catch (ValidationException e) { 64 | assertThat(e.getMessage(), is("error.name")); 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /micro-core/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: test 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | extension: 8 | datasource: 9 | default.jpa.show-sql: true 10 | system.jpa.show-sql: true 11 | security.auth.enabled: false 12 | -------------------------------------------------------------------------------- /micro-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /micro-registry/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /micro-registry/README.md: -------------------------------------------------------------------------------- 1 | micro-registry 2 | --- 3 | 4 | シンプルなサービスレジストリ ( Eureka ) プロセスです。 5 | 6 | - 他プロジェクトに依存しないシンプルな Spring Boot プロセスです。 7 | - その特性上、必ず最初に起動しておく必要があります。 8 | - テスト環境ではDBサーバとしても使っています。 9 | -------------------------------------------------------------------------------- /micro-registry/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | mainClassName = 'sample.MicroRegistry' 3 | } 4 | 5 | dependencies { 6 | implementation "org.springframework.cloud:spring-cloud-starter-netflix-eureka-server" 7 | implementation "com.h2database:h2" 8 | } 9 | -------------------------------------------------------------------------------- /micro-registry/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-registry/libs/.gitkeep -------------------------------------------------------------------------------- /micro-registry/src/main/java/sample/MicroRegistry.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import java.sql.SQLException; 4 | 5 | import org.h2.tools.Server; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 10 | import org.springframework.context.annotation.*; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | 14 | /** 15 | * Eureka Server の起動クラス。 16 | *

本クラスを実行する事でSpringBootが提供する組込Tomcatでのアプリケーション起動が行われます。 17 | */ 18 | @SpringBootApplication 19 | @EnableEurekaServer 20 | public class MicroRegistry { 21 | 22 | public static void main(String[] args) { 23 | new SpringApplicationBuilder(MicroRegistry.class) 24 | .run(args); 25 | } 26 | 27 | /** プロセススコープの拡張定義を表現します。 */ 28 | @Configuration 29 | static class ProcessAutoConfig { 30 | 31 | /** テスト用途のメモリDBサーバ */ 32 | @Bean(initMethod="start", destroyMethod = "stop") 33 | @ConditionalOnProperty(prefix = "extension.test.db", name = "enabled", matchIfMissing = false) 34 | Server h2Server() { 35 | try { 36 | return Server.createTcpServer("-tcpAllowOthers", "-ifNotExists", "-tcpPort", "9092"); 37 | } catch (SQLException e) { 38 | throw new IllegalStateException(e); 39 | } 40 | } 41 | } 42 | 43 | @Configuration 44 | static class AuthSecurityConfig extends WebSecurityConfigurerAdapter { 45 | 46 | @Override 47 | protected void configure(HttpSecurity http) throws Exception { 48 | // レジストリサーバでは認証を常に許可 49 | http.csrf().disable() 50 | .authorizeRequests() 51 | .mvcMatchers("/**").permitAll(); 52 | } 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /micro-registry/src/main/java/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーションルートディレクトリ。 3 | *

Applicationクラスを実行する事でアプリケーションプロセスが立ち上がります。 4 | */ 5 | package sample; -------------------------------------------------------------------------------- /micro-registry/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | application.name: micro-registry 4 | banner.location: banner.txt 5 | jackson.serialization: 6 | indent-output: true 7 | jpa.open-in-view: false 8 | security.user.password: unused 9 | autoconfigure.exclude: 10 | - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration 11 | cloud.loadbalancer.ribbon.enabled: false 12 | 13 | server: 14 | port: 8761 15 | 16 | management: 17 | endpoints.web: 18 | base-path: /management 19 | exposure.include: "*" 20 | 21 | eureka: 22 | instance: 23 | hostname: localhost 24 | lease-renewal-interval-in-seconds: 5 25 | status-page-url-path: ${management.endpoints.web.base-path}/info 26 | health-check-url-path: ${management.endpoints.web.base-path}/health 27 | prefer-ip-address: true 28 | client: 29 | register-with-eureka: false 30 | fetch-registry: false 31 | 32 | extension.test.db.enabled: true 33 | 34 | --- 35 | spring: 36 | profiles: production 37 | 38 | extension: 39 | security.cors.enabled: false 40 | test.db.enabled: false 41 | -------------------------------------------------------------------------------- /micro-registry/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ====================================================================================== 2 | Start Application [ micro-registry ] 3 | -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /micro-registry/src/test/java/sample/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-registry/src/test/java/sample/.gitkeep -------------------------------------------------------------------------------- /micro-registry/src/test/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-registry/src/test/resources/.gitkeep -------------------------------------------------------------------------------- /micro-web/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | .classpath 4 | .project 5 | /bin 6 | /build 7 | .idea 8 | *.iml 9 | -------------------------------------------------------------------------------- /micro-web/README.md: -------------------------------------------------------------------------------- 1 | micro-web 2 | --- 3 | 4 | 外部に公開する API フロントプロセスです。 5 | 6 | - 基本的に処理は全て `micro-app` へ流します。 7 | - 流す対象は `micro-registry` から見つけ出します。 8 | - Ribbon を経由させてロードバランシングさせています。 9 | - UI リソースも同梱させたい時は、 UI リソースのビルド先を `main/resources/static` 直下にしてください。 10 | -------------------------------------------------------------------------------- /micro-web/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { 2 | mainClassName = 'sample.MicroWeb' 3 | } 4 | 5 | dependencies { 6 | implementation project(':micro-core') 7 | implementation project(':micro-asset') 8 | } 9 | -------------------------------------------------------------------------------- /micro-web/libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkazama/sample-boot-micro/0e8fb571af8d0ab32d22cfba33ab2eab48836381/micro-web/libs/.gitkeep -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/MicroWeb.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.cache.annotation.EnableCaching; 6 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.context.annotation.Import; 8 | 9 | import sample.api.AssetFacadeInvoker; 10 | import sample.controller.AccountController; 11 | 12 | /** 13 | * Webフロントプロセスの起動クラス。 14 | *

本クラスを実行する事でSpringBootが提供する組込Tomcatでのアプリケーション起動が行われます。 15 | *

自動設定対象として以下のパッケージをスキャンしています。 16 | *

    17 | *
  • sample.api 18 | *
  • sample.controller 19 | *
20 | */ 21 | @SpringBootApplication(scanBasePackageClasses = { 22 | AssetFacadeInvoker.class, AccountController.class }) 23 | @Import(MicroWebConfig.class) 24 | @EnableCaching(proxyTargetClass = true) 25 | @EnableDiscoveryClient 26 | public class MicroWeb { 27 | 28 | public static void main(String[] args) { 29 | new SpringApplicationBuilder(MicroWeb.class) 30 | .profiles("web") 31 | .run(args); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/MicroWebConfig.java: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.cloud.client.loadbalancer.LoadBalanced; 6 | import org.springframework.context.annotation.*; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | 11 | import sample.api.ApiClient; 12 | import sample.context.actor.ActorSession; 13 | import sample.context.rest.RestActorSessionInterceptor; 14 | import sample.usecase.SecurityService; 15 | 16 | /** 17 | * アプリケーションのセキュリティ定義を表現します。 18 | */ 19 | @Configuration 20 | @Import({ApplicationConfig.class, SecurityService.class}) 21 | public class MicroWebConfig { 22 | 23 | /** Spring Cloud 関連の定義を表現します。 */ 24 | @Configuration 25 | static class DiscoveryAutoConfig { 26 | 27 | @Bean 28 | ApiClient apiClient(RestTemplate template, ObjectMapper mapper) { 29 | return ApiClient.of(template, mapper); 30 | } 31 | 32 | /** 33 | * Ribbon 経由で Eureka がサポートしているサービスを実行するための RestTemplate。 34 | *

リクエスト時に利用者情報を紐づけています。 35 | */ 36 | @Bean 37 | @LoadBalanced 38 | RestTemplate restTemplate(ActorSession session) { 39 | RestTemplate tmpl = new RestTemplate(); 40 | tmpl.setInterceptors(new ArrayList<>(Arrays.asList( 41 | new RestActorSessionInterceptor(session) 42 | ))); 43 | return tmpl; 44 | } 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/AssetFacadeInvoker.java: -------------------------------------------------------------------------------- 1 | package sample.api; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | 10 | import sample.context.rest.RestInvoker; 11 | import sample.microasset.api.AssetFacade; 12 | import sample.microasset.model.asset.CashInOut; 13 | import sample.microasset.model.asset.CashInOut.RegCashOut; 14 | 15 | /** 16 | * 資産系ユースケースの API 実行処理を表現します。 17 | */ 18 | @Component 19 | public class AssetFacadeInvoker implements AssetFacade { 20 | 21 | private final ApiClient client; 22 | private final String url; 23 | 24 | public AssetFacadeInvoker( 25 | ApiClient client, 26 | @Value("${extension.remoting.asset}") String url) { 27 | this.client = client; 28 | this.url = url; 29 | } 30 | 31 | /** {@inheritDoc} */ 32 | @Override 33 | public List findUnprocessedCashOut() { 34 | return invoker().get(PathFindUnprocessedCashOut, new ParameterizedTypeReference>() { 35 | }); 36 | } 37 | 38 | private RestInvoker invoker() { 39 | return client.invoker(url, Path); 40 | } 41 | 42 | /** {@inheritDoc} */ 43 | @Override 44 | public ResponseEntity withdraw(RegCashOut p) { 45 | return invoker().postEntity(PathWithdraw, Long.class, p); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/admin/AssetAdminFacadeInvoker.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | 10 | import sample.api.*; 11 | import sample.context.rest.RestInvoker; 12 | import sample.microasset.api.admin.AssetAdminFacade; 13 | import sample.microasset.model.asset.CashInOut; 14 | import sample.microasset.model.asset.CashInOut.FindCashInOut; 15 | 16 | /** 17 | * 資産系社内ユースケースの API 実行処理を表現します。 18 | */ 19 | @Component 20 | public class AssetAdminFacadeInvoker implements AssetAdminFacade { 21 | 22 | private final ApiClient client; 23 | private final String appliationName; 24 | 25 | public AssetAdminFacadeInvoker( 26 | ApiClient client, 27 | @Value("${extension.remoting.asset}") String applicationName) { 28 | this.client = client; 29 | this.appliationName = applicationName; 30 | } 31 | 32 | /** {@inheritDoc} */ 33 | @Override 34 | public List findCashInOut(FindCashInOut p) { 35 | return invoker().get(PathFindCashInOut, new ParameterizedTypeReference>() {}, p); 36 | } 37 | 38 | private RestInvoker invoker() { 39 | return client.invoker(appliationName, Path); 40 | } 41 | 42 | /** {@inheritDoc} */ 43 | @Override 44 | public ResponseEntity closingCashOut() { 45 | return invoker().postEntity(PathClosingCashOut, Void.class, null); 46 | } 47 | 48 | /** {@inheritDoc} */ 49 | @Override 50 | public ResponseEntity realizeCashflow() { 51 | return invoker().postEntity(PathRealizeCashflow, Void.class, null); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/admin/MasterAdminFacadeInvoker.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | 10 | import sample.api.*; 11 | import sample.context.rest.RestInvoker; 12 | import sample.model.master.*; 13 | import sample.model.master.Holiday.RegHoliday; 14 | 15 | /** 16 | * マスタ系社内ユースケースの API 実行処理を表現します。 17 | */ 18 | @Component 19 | public class MasterAdminFacadeInvoker implements MasterAdminFacade { 20 | 21 | private final ApiClient client; 22 | private final String appliationName; 23 | 24 | public MasterAdminFacadeInvoker( 25 | ApiClient client, 26 | @Value("${extension.remoting.app}") String applicationName) { 27 | this.client = client; 28 | this.appliationName = applicationName; 29 | } 30 | 31 | /** {@inheritDoc} */ 32 | @Override 33 | public Staff getStaff(String staffId) { 34 | return invoker().get(PathGetStaff, Staff.class, staffId); 35 | } 36 | 37 | private RestInvoker invoker() { 38 | return client.invoker(appliationName, Path); 39 | } 40 | 41 | /** {@inheritDoc} */ 42 | @Override 43 | public List findStaffAuthority(String staffId) { 44 | return invoker().get(PathFindStaffAuthority, new ParameterizedTypeReference>() {}, staffId); 45 | } 46 | 47 | /** {@inheritDoc} */ 48 | @Override 49 | public ResponseEntity registerHoliday(RegHoliday p) { 50 | return invoker().postEntity(PathRegisterHoliday, Void.class, p); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/admin/SystemAdminFacadeInvoker.java: -------------------------------------------------------------------------------- 1 | package sample.api.admin; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.core.ParameterizedTypeReference; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.stereotype.Component; 9 | 10 | import sample.api.ApiClient; 11 | import sample.context.AppSetting; 12 | import sample.context.AppSetting.FindAppSetting; 13 | import sample.context.audit.*; 14 | import sample.context.audit.AuditActor.FindAuditActor; 15 | import sample.context.audit.AuditEvent.FindAuditEvent; 16 | import sample.context.orm.PagingList; 17 | import sample.context.rest.RestInvoker; 18 | 19 | /** 20 | * システム系社内ユースケースの API 実行処理を表現します。 21 | */ 22 | @Component 23 | public class SystemAdminFacadeInvoker implements SystemAdminFacade { 24 | 25 | private final ApiClient client; 26 | private final String appliationName; 27 | 28 | public SystemAdminFacadeInvoker( 29 | ApiClient client, 30 | @Value("${extension.remoting.app}") String applicationName) { 31 | this.client = client; 32 | this.appliationName = applicationName; 33 | } 34 | 35 | /** {@inheritDoc} */ 36 | @Override 37 | public PagingList findAuditActor(FindAuditActor p) { 38 | return invoker().get(PathFindAudiActor, new ParameterizedTypeReference>() { 39 | }, p); 40 | } 41 | 42 | private RestInvoker invoker() { 43 | return client.invoker(appliationName, Path); 44 | } 45 | 46 | /** {@inheritDoc} */ 47 | @Override 48 | public PagingList findAuditEvent(FindAuditEvent p) { 49 | return invoker().get(PathFindAudiEvent, new ParameterizedTypeReference>() { 50 | }, p); 51 | } 52 | 53 | /** {@inheritDoc} */ 54 | @Override 55 | public List findAppSetting(FindAppSetting p) { 56 | return invoker().get(PathFindAppSetting, new ParameterizedTypeReference>() { 57 | }, p); 58 | } 59 | 60 | /** {@inheritDoc} */ 61 | @Override 62 | public ResponseEntity changeAppSetting(String id, String value) { 63 | return invoker().postEntity(PathChangeAppSetting, Void.class, null, id, value); 64 | } 65 | 66 | /** {@inheritDoc} */ 67 | @Override 68 | public ResponseEntity processDay() { 69 | return invoker().postEntity(PathProcessDay, Void.class, null); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/admin/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する社内 API コンポーネント。 3 | */ 4 | package sample.api.admin; -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 外部へ公開する API コンポーネント。 3 | */ 4 | package sample.api; -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package sample.controller; 2 | 3 | import java.util.*; 4 | 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import lombok.*; 8 | import sample.ValidationException; 9 | import sample.ValidationException.ErrorKeys; 10 | import sample.context.actor.Actor; 11 | import sample.context.security.*; 12 | import sample.context.security.SecurityActorFinder.ActorDetails; 13 | 14 | /** 15 | * 口座に関わる顧客のUI要求を処理します。 16 | */ 17 | @RestController 18 | @RequestMapping("/api/account") 19 | public class AccountController { 20 | 21 | private final SecurityProperties securityProps; 22 | 23 | public AccountController(SecurityProperties securityProps) { 24 | this.securityProps = securityProps; 25 | } 26 | 27 | /** ログイン状態を確認します。 */ 28 | @GetMapping("/loginStatus") 29 | public boolean loginStatus() { 30 | return true; 31 | } 32 | 33 | /** 口座ログイン情報を取得します。 */ 34 | @GetMapping("/loginAccount") 35 | public LoginAccount loadLoginAccount() { 36 | if (securityProps.auth().isEnabled()) { 37 | ActorDetails actorDetails = SecurityActorFinder.actorDetails() 38 | .orElseThrow(() -> new ValidationException(ErrorKeys.Authentication)); 39 | Actor actor = actorDetails.actor(); 40 | return new LoginAccount(actor.getId(), actor.getName(), actorDetails.getAuthorityIds()); 41 | } else { // for dummy login 42 | return new LoginAccount("sample", "sample", new ArrayList<>()); 43 | } 44 | } 45 | 46 | /** クライアント利用用途に絞ったパラメタ */ 47 | @Data 48 | @NoArgsConstructor 49 | @AllArgsConstructor 50 | static class LoginAccount { 51 | private String id; 52 | private String name; 53 | private Collection authorities; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/AssetController.java: -------------------------------------------------------------------------------- 1 | package sample.controller; 2 | 3 | import static sample.microasset.api.AssetFacade.*; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.*; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | import javax.validation.Valid; 11 | 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import lombok.Value; 16 | import sample.ActionStatusType; 17 | import sample.context.Dto; 18 | import sample.microasset.api.AssetFacade; 19 | import sample.microasset.model.asset.CashInOut; 20 | import sample.microasset.model.asset.CashInOut.RegCashOut; 21 | 22 | /** 23 | * 資産に関わる顧客のUI要求を処理します。 24 | */ 25 | @RestController 26 | @RequestMapping(Path) 27 | public class AssetController { 28 | 29 | private final AssetFacade facade; 30 | 31 | public AssetController(AssetFacade facade) { 32 | this.facade = facade; 33 | } 34 | 35 | /** 未処理の振込依頼情報を検索します。 */ 36 | @GetMapping(PathFindUnprocessedCashOut) 37 | public List findUnprocessedCashOut() { 38 | return facade.findUnprocessedCashOut().stream() 39 | .map((cio) -> CashOutUI.of(cio)) 40 | .collect(Collectors.toList()); 41 | } 42 | 43 | /** 振込出金依頼をします。 */ 44 | @PostMapping(PathWithdraw) 45 | public ResponseEntity withdraw(@Valid RegCashOut p) { 46 | return facade.withdraw(p); 47 | } 48 | 49 | /** 振込出金依頼情報の表示用Dto */ 50 | @Value 51 | static class CashOutUI implements Dto { 52 | private static final long serialVersionUID = 1L; 53 | private Long id; 54 | private String currency; 55 | private BigDecimal absAmount; 56 | private LocalDate requestDay; 57 | private LocalDateTime requestDate; 58 | private LocalDate eventDay; 59 | private LocalDate valueDay; 60 | private ActionStatusType statusType; 61 | private LocalDateTime updateDate; 62 | private Long cashflowId; 63 | 64 | public static CashOutUI of(final CashInOut cio) { 65 | return new CashOutUI(cio.getId(), cio.getCurrency(), cio.getAbsAmount(), cio.getRequestDay(), 66 | cio.getRequestDate(), cio.getEventDay(), cio.getValueDay(), cio.getStatusType(), 67 | cio.getUpdateDate(), cio.getCashflowId()); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package sample.controller; 2 | 3 | import org.aspectj.lang.annotation.*; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.stereotype.Component; 8 | 9 | import sample.context.actor.*; 10 | import sample.context.actor.Actor.ActorRoleType; 11 | 12 | /** 13 | * Spring Securityの設定状況に応じてスレッドローカルへ利用者を紐付けるAOPInterceptor。 14 | */ 15 | @Aspect 16 | @Configuration 17 | public class LoginInterceptor { 18 | 19 | @Autowired 20 | ActorSession session; 21 | 22 | @Before("execution(* *..controller.system.*Controller.*(..))") 23 | public void bindSystem() { 24 | session.bind(Actor.System); 25 | } 26 | 27 | @After("execution(* *..controller..*Controller.*(..))") 28 | public void unbind() { 29 | session.unbind(); 30 | } 31 | 32 | /** 33 | * セキュリティの認証設定(extension.security.auth.enabled)が無効時のみ有効される擬似ログイン処理。 34 | *

開発時のみ利用してください。 35 | */ 36 | @Aspect 37 | @Component 38 | @ConditionalOnProperty(name = "extension.security.auth.enabled", havingValue = "false", matchIfMissing = false) 39 | public static class DummyLoginInterceptor { 40 | @Autowired 41 | ActorSession session; 42 | 43 | @Before("execution(* *..controller.*Controller.*(..))") 44 | void bindUser() { 45 | session.bind(new Actor("sample", ActorRoleType.User)); 46 | } 47 | 48 | @Before("execution(* *..controller.admin.*Controller.*(..))") 49 | void bindAdmin() { 50 | session.bind(new Actor("admin", ActorRoleType.Internal)); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/admin/AssetAdminController.java: -------------------------------------------------------------------------------- 1 | package sample.controller.admin; 2 | 3 | import static sample.microasset.api.admin.AssetAdminFacade.*; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import sample.microasset.api.admin.AssetAdminFacade; 12 | import sample.microasset.model.asset.CashInOut; 13 | import sample.microasset.model.asset.CashInOut.FindCashInOut; 14 | 15 | /** 16 | * 資産に関わる社内のUI要求を処理します。 17 | */ 18 | @RestController 19 | @RequestMapping(Path) 20 | public class AssetAdminController { 21 | 22 | private final AssetAdminFacade facade; 23 | 24 | public AssetAdminController(AssetAdminFacade facade) { 25 | this.facade = facade; 26 | } 27 | 28 | /** 未処理の振込依頼情報を検索します。 */ 29 | @GetMapping(PathFindCashInOut) 30 | public List findCashInOut(@Valid FindCashInOut p) { 31 | return facade.findCashInOut(p); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/admin/MasterAdminController.java: -------------------------------------------------------------------------------- 1 | package sample.controller.admin; 2 | 3 | import static sample.api.admin.MasterAdminFacade.*; 4 | 5 | import java.util.*; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import lombok.*; 13 | import sample.ValidationException; 14 | import sample.ValidationException.ErrorKeys; 15 | import sample.api.admin.MasterAdminFacade; 16 | import sample.context.actor.Actor; 17 | import sample.context.security.*; 18 | import sample.context.security.SecurityActorFinder.ActorDetails; 19 | import sample.model.master.Holiday.RegHoliday; 20 | 21 | /** 22 | * マスタに関わる社内のUI要求を処理します。 23 | */ 24 | @RestController 25 | @RequestMapping(Path) 26 | public class MasterAdminController { 27 | 28 | private final SecurityProperties securityProps; 29 | private final MasterAdminFacade facade; 30 | 31 | public MasterAdminController(SecurityProperties securityProps, MasterAdminFacade facade) { 32 | this.securityProps = securityProps; 33 | this.facade = facade; 34 | } 35 | 36 | /** 社員ログイン状態を確認します。 */ 37 | @RequestMapping("/loginStatus") 38 | public boolean loginStatus() { 39 | return true; 40 | } 41 | 42 | /** 社員ログイン情報を取得します。 */ 43 | @GetMapping("/loginStaff") 44 | public LoginStaff loadLoginStaff() { 45 | if (securityProps.auth().isEnabled()) { 46 | ActorDetails actorDetails = SecurityActorFinder.actorDetails() 47 | .orElseThrow(() -> new ValidationException(ErrorKeys.Authentication)); 48 | Actor actor = actorDetails.actor(); 49 | return new LoginStaff(actor.getId(), actor.getName(), actorDetails.getAuthorityIds()); 50 | } else { // for dummy login 51 | return new LoginStaff("sample", "sample", new ArrayList<>()); 52 | } 53 | } 54 | 55 | /** クライアント利用用途に絞ったパラメタ */ 56 | @Data 57 | @NoArgsConstructor 58 | @AllArgsConstructor 59 | static class LoginStaff { 60 | private String id; 61 | private String name; 62 | private Collection authorities; 63 | } 64 | 65 | /** 休日を登録します。 */ 66 | @PostMapping(PathRegisterHoliday) 67 | public ResponseEntity registerHoliday(@Valid RegHoliday p) { 68 | return facade.registerHoliday(p); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/admin/SystemAdminController.java: -------------------------------------------------------------------------------- 1 | package sample.controller.admin; 2 | 3 | import static sample.api.admin.SystemAdminFacade.*; 4 | 5 | import java.util.List; 6 | 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import sample.api.admin.SystemAdminFacade; 13 | import sample.context.AppSetting; 14 | import sample.context.AppSetting.FindAppSetting; 15 | import sample.context.audit.*; 16 | import sample.context.audit.AuditActor.FindAuditActor; 17 | import sample.context.audit.AuditEvent.FindAuditEvent; 18 | import sample.context.orm.PagingList; 19 | 20 | /** 21 | * システムに関わる社内のUI要求を処理します。 22 | */ 23 | @RestController 24 | @RequestMapping(Path) 25 | public class SystemAdminController { 26 | 27 | private final SystemAdminFacade facade; 28 | 29 | public SystemAdminController(SystemAdminFacade facade) { 30 | this.facade = facade; 31 | } 32 | 33 | /** 利用者監査ログを検索します。 */ 34 | @GetMapping(PathFindAudiActor) 35 | public PagingList findAuditActor(@Valid FindAuditActor p) { 36 | return facade.findAuditActor(p); 37 | } 38 | 39 | /** イベント監査ログを検索します。 */ 40 | @GetMapping(PathFindAudiEvent) 41 | public PagingList findAuditEvent(@Valid FindAuditEvent p) { 42 | return facade.findAuditEvent(p); 43 | } 44 | 45 | /** アプリケーション設定一覧を検索します。 */ 46 | @GetMapping(PathFindAppSetting) 47 | public List findAppSetting(@Valid FindAppSetting p) { 48 | return facade.findAppSetting(p); 49 | } 50 | 51 | /** アプリケーション設定情報を変更します。 */ 52 | @PostMapping(PathChangeAppSetting) 53 | public ResponseEntity changeAppSetting(@PathVariable String id, String value) { 54 | return facade.changeAppSetting(id, value); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/admin/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 社内管理向けのUI層コンポーネント。 3 | */ 4 | package sample.controller.admin; -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * UI層のコンポーネントを管理します。 3 | *

アプリケーション層のコンポーネントを利用してユースケース処理を実現します。 4 | * UI層のコンポーネントが直接ドメイン層を呼び出す事やController同士で相互依存することは想定していません。
5 | * ※EntityについてはDTOとして解釈することで許容しますが、必要以上の情報を返してセキュリティリスクを生まぬよう注意する必要があります。 6 | */ 7 | package sample.controller; -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/system/JobController.java: -------------------------------------------------------------------------------- 1 | package sample.controller.system; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | 6 | import sample.api.admin.SystemAdminFacade; 7 | import sample.microasset.api.admin.AssetAdminFacade; 8 | 9 | /** 10 | * システムジョブのUI要求を処理します。 11 | * low: 通常はバッチプロセス(または社内プロセスに内包)を別途作成して、ジョブスケジューラから実行される方式になります。 12 | * ジョブの負荷がオンライン側へ影響を与えないよう事前段階の設計が重要になります。 13 | * low: 社内/バッチプロセス切り出す場合はVM分散時の情報/排他同期を意識する必要があります。(DB同期/メッセージング同期/分散製品の利用 等) 14 | */ 15 | @RestController 16 | @RequestMapping("/api/system/job") 17 | public class JobController { 18 | 19 | private final AssetAdminFacade asset; 20 | private final SystemAdminFacade system; 21 | 22 | public JobController(AssetAdminFacade asset, SystemAdminFacade system) { 23 | this.asset = asset; 24 | this.system = system; 25 | } 26 | 27 | /** 営業日を進めます。 */ 28 | @PostMapping("/daily/processDay") 29 | public ResponseEntity processDay() { 30 | return system.processDay(); 31 | } 32 | 33 | /** 振込出金依頼を締めます。 */ 34 | @PostMapping("/daily/closingCashOut") 35 | public ResponseEntity closingCashOut() { 36 | return asset.closingCashOut(); 37 | } 38 | 39 | /** キャッシュフローを実現します。 */ 40 | @PostMapping("/daily/realizeCashflow") 41 | public ResponseEntity realizeCashflow() { 42 | return asset.realizeCashflow(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/controller/system/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * システム管理向けのUI層コンポーネント。 3 | */ 4 | package sample.controller.system; -------------------------------------------------------------------------------- /micro-web/src/main/java/sample/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * アプリケーションルートディレクトリ。 3 | *

Applicationクラスを実行する事でアプリケーションプロセスが立ち上がります。 4 | */ 5 | package sample; -------------------------------------------------------------------------------- /micro-web/src/main/resources/application-web.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: web 4 | application.name: micro-web 5 | 6 | server: 7 | port: 8080 8 | 9 | eureka.client: 10 | register-with-eureka: false 11 | 12 | extension: 13 | security: 14 | auth: 15 | enabled: false 16 | admin: false 17 | cors.enabled: true 18 | mail.enabled: false 19 | remoting: 20 | app: ${APPLICATION_APP_URL:http://micro-app} 21 | asset: ${APPLICATION_ASSET_URL:http://micro-asset} 22 | 23 | --- 24 | spring: 25 | profiles: production 26 | 27 | extension: 28 | security: 29 | auth.enabled: true 30 | cors.enabled: false 31 | 32 | --- 33 | spring: 34 | profiles: admin 35 | 36 | server.port: 8081 37 | 38 | extension: 39 | security.auth.admin: true 40 | -------------------------------------------------------------------------------- /micro-web/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ====================================================================================== 2 | Start Application [ micro-web ] 3 | -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /micro-web/src/test/resources/application-testweb.yml: -------------------------------------------------------------------------------- 1 | --- 2 | spring: 3 | profiles: testweb 4 | 5 | logging.config: classpath:logback.xml 6 | 7 | eureka.client.enabled: false 8 | 9 | extension: 10 | datasource: 11 | default: 12 | url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 13 | username: 14 | password: 15 | jpa.hibernate.ddl-auto: none 16 | system: 17 | url: jdbc:h2:mem:system;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 18 | username: 19 | password: 20 | jpa.hibernate.ddl-auto: none 21 | security.auth.enabled: false 22 | -------------------------------------------------------------------------------- /micro-web/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'micro-core', 'micro-registry', 'micro-app', 'micro-asset', 'micro-web' 2 | --------------------------------------------------------------------------------