list;
20 | private Pagination page;
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/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-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-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-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-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-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/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/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-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-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-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-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/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-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-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-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-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-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-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-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/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/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/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/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-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 extends Payload>[] 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 extends Payload>[] 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-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-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/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-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-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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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-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-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 | *
18 | * - sample.usecase
19 | *
- sample.api
20 | *
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-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/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/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 extends Payload>[] 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 extends Payload>[] 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/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/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 extends Payload>[] 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 extends Payload>[] 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/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 extends Payload>[] 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-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 | *
18 | * - sample.microasset.usecase
19 | *
- sample.microasset.api
20 | *
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-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 extends Payload>[] 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/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/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 extends Payload>[] 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/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/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 extends Payload>[] 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-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-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 extends Payload>[] 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 extends Payload>[] 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/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 extends Payload>[] 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/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 extends Payload>[] 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-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-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/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 extends Payload>[] 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/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 extends Payload>[] 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 extends Payload>[] 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/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 extends Payload>[] 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/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 extends Payload>[] 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/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 extends Payload>[] 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/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/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/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 extends Payload>[] 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-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-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/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 extends Payload>[] 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/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 extends Payload>[] 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/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 extends Payload>[] 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/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 extends Payload>[] 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-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-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/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/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-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-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/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-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-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-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-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-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-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-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/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/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-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-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/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-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-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-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-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