当前位置:首页 > 软件设计 > 正文内容

范畴驱动规划之银行转账:Wow结构实战

邻居的猫1个月前 (12-09)软件设计1952

范畴驱动规划之银行转账:Wow结构实战

银行账户转账事例是一个经典的范畴驱动规划(DDD)运用场景。接下来咱们经过一个简略的银行账户转账事例,来了解怎么运用 Wow 进行范畴驱动规划以及服务开发。

银行转账流程

  1. 预备转账(Prepare): 用户建议转账恳求,触发 Prepare 过程。这个过程会向源账户发送预备转账的恳求。
  2. 校验余额(CheckBalance): 源账户在收到预备转账恳求后,会履行校验余额的操作,确保账户有满足的余额进行转账。
  3. 确定金额(LockAmount): 假如余额满足,源账户会确定转账金额,避免其他操作搅扰。
  4. 入账(Entry): 接着,转账流程进入到方针账户,履行入账操作。
  5. 承认转账(Confirm): 假如入账成功,承认转账;不然,履行解锁金额操作。
    1. 成功途径(Success): 假如一切顺利,完结转账流程。
    2. 失利途径(Fail): 假如入账失利,履行解锁金额操作,并处理失利状况。

Saga-Transfer

运转事例

  • 运转 TransferExampleServer.java
  • 检查 Swagger-UI : http://localhost:8080/swagger-ui.html
  • 履行 API 测验:Transfer.http

主动生成 API 端点

运转之后,拜访 Swagger-UI : http://localhost:8080/swagger-ui.html 。
该 RESTful API 端点是由 Wow 主动生成的,无需手动编写。

Wow-Transfer

模块区分

模块 阐明
example-transfer-api API 层,界说聚合指令(Command)、范畴事情(Domain Event)以及查询视图模型(Query View Model),这个模块充当了各个模块之间通讯的“发布言语”。
example-transfer-domain 范畴层,包含聚合根和事务束缚的完成。聚合根:范畴模型的进口点,担任和谐范畴目标的操作。事务束缚:包含验证规矩、范畴事情的处理等。
example-transfer-server 宿主服务,运用程序的发动点。担任整合其他模块,并提供运用程序的进口。触及装备依靠项、衔接数据库、发动 API 服务

范畴建模

状况聚合根(AccountState)与指令聚合根(Account)别离规划确保了在履行指令过程中,不会修正状况聚合根的状况。

状况聚合根(AccountState)建模

public class AccountState implements Identifier {
    private final String id;
    private String name;
    /**
     * 余额
     */
    private long balanceAmount = 0L;
    /**
     * 已确定金额
     */
    private long lockedAmount = 0L;
    /**
     * 账号已冻住标记
     */
    private boolean frozen = false;

    @JsonCreator
    public AccountState(@JsonProperty("id") String id) {
        this.id = id;
    }

    @NotNull
    @Override
    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public long getBalanceAmount() {
        return balanceAmount;
    }

    public long getLockedAmount() {
        return lockedAmount;
    }

    public boolean isFrozen() {
        return frozen;
    }

    void onSourcing(AccountCreated accountCreated) {
        this.name = accountCreated.name();
        this.balanceAmount = accountCreated.balance();
    }

    void onSourcing(AmountLocked amountLocked) {
        balanceAmount = balanceAmount - amountLocked.amount();
        lockedAmount = lockedAmount + amountLocked.amount();
    }

    void onSourcing(AmountEntered amountEntered) {
        balanceAmount = balanceAmount + amountEntered.amount();
    }

    void onSourcing(Confirmed confirmed) {
        lockedAmount = lockedAmount - confirmed.amount();
    }

    void onSourcing(AmountUnlocked amountUnlocked) {
        lockedAmount = lockedAmount - amountUnlocked.amount();
        balanceAmount = balanceAmount + amountUnlocked.amount();
    }

    void onSourcing(AccountFrozen accountFrozen) {
        this.frozen = true;
    }

}

指令聚合根(Account)建模


@StaticTenantId
@AggregateRoot
public class Account {
    private final AccountState state;

    public Account(AccountState state) {
        this.state = state;
    }

    AccountCreated onCommand(CreateAccount createAccount) {
        return new AccountCreated(createAccount.name(), createAccount.balance());
    }

    @OnCommand(returns = {AmountLocked.class, Prepared.class})
    List<?> onCommand(Prepare prepare) {
        checkBalance(prepare.amount());
        return List.of(new AmountLocked(prepare.amount()), new Prepared(prepare.to(), prepare.amount()));
    }

    private void checkBalance(long amount) {
        if (state.isFrozen()) {
            throw new IllegalStateException("账号已冻住无法转账.");
        }
        if (state.getBalanceAmount() < amount) {
            throw new IllegalStateException("账号余额缺乏.");
        }
    }

    Object onCommand(Entry entry) {
        if (state.isFrozen()) {
            return new EntryFailed(entry.sourceId(), entry.amount());
        }
        return new AmountEntered(entry.sourceId(), entry.amount());
    }

    Confirmed onCommand(Confirm confirm) {
        return new Confirmed(confirm.amount());
    }

    AmountUnlocked onCommand(UnlockAmount unlockAmount) {
        return new AmountUnlocked(unlockAmount.amount());
    }

    AccountFrozen onCommand(FreezeAccount freezeAccount) {
        return new AccountFrozen(freezeAccount.reason());
    }
}

转账流程管理器(TransferSaga

转账流程管理器(TransferSaga)担任和谐处理转账的事情,并生成相应的指令。

  • onEvent(Prepared): 订阅转账已预备就绪事情(Prepared),并生成入账指令(Entry)。
  • onEvent(AmountEntered): 订阅转账已入账事情(AmountEntered),并生成承认转账指令(Confirm)。
  • onEvent(EntryFailed): 订阅转账入账失利事情(EntryFailed),并生成解锁金额指令(UnlockAmount)。

@StatelessSaga
public class TransferSaga {

    Entry onEvent(Prepared prepared, AggregateId aggregateId) {
        return new Entry(prepared.to(), aggregateId.getId(), prepared.amount());
    }

    Confirm onEvent(AmountEntered amountEntered) {
        return new Confirm(amountEntered.sourceId(), amountEntered.amount());
    }

    UnlockAmount onEvent(EntryFailed entryFailed) {
        return new UnlockAmount(entryFailed.sourceId(), entryFailed.amount());
    }
}

单元测验

凭借 Wow 单元测验套件,能够轻松的编写聚合根和 Saga 的单元测验。然后提高代码覆盖率,确保代码质量。

example-transfer-jacoco

运用 aggregateVerifier 进行聚合根单元测验,能够有用的削减单元测验的编写工作量。

Account 聚合根单元测验

internal class AccountKTest {
    @Test
    fun createAccount() {
        aggregateVerifier<Account, AccountState>()
            .given()
            .`when`(CreateAccount("name", 100))
            .expectEventType(AccountCreated::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
            }
            .verify()
    }

    @Test
    fun prepare() {
        aggregateVerifier<Account, AccountState>()
            .given(AccountCreated("name", 100))
            .`when`(Prepare("name", 100))
            .expectEventType(AmountLocked::class.java, Prepared::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(0))
            }
            .verify()
    }

    @Test
    fun entry() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100))
            .`when`(Entry(aggregateId, "sourceId", 100))
            .expectEventType(AmountEntered::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(200))
            }
            .verify()
    }

    @Test
    fun entryGivenFrozen() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AccountFrozen(""))
            .`when`(Entry(aggregateId, "sourceId", 100))
            .expectEventType(EntryFailed::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.isFrozen, equalTo(true))
            }
            .verify()
    }

    @Test
    fun confirm() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AmountLocked(100))
            .`when`(Confirm(aggregateId, 100))
            .expectEventType(Confirmed::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(0))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(false))
            }
            .verify()
    }

    @Test
    fun unlockAmount() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100), AmountLocked(100))
            .`when`(UnlockAmount(aggregateId, 100))
            .expectEventType(AmountUnlocked::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(false))
            }
            .verify()
    }

    @Test
    fun freezeAccount() {
        val aggregateId = GlobalIdGenerator.generateAsString()
        aggregateVerifier<Account, AccountState>(aggregateId)
            .given(AccountCreated("name", 100))
            .`when`(FreezeAccount(""))
            .expectEventType(AccountFrozen::class.java)
            .expectState {
                assertThat(it.name, equalTo("name"))
                assertThat(it.balanceAmount, equalTo(100))
                assertThat(it.lockedAmount, equalTo(0))
                assertThat(it.isFrozen, equalTo(true))
            }
            .verify()
    }
}

运用 sagaVerifier 进行 Saga 单元测验,能够有用的削减单元测验的编写工作量。

TransferSaga 单元测验

internal class TransferSagaTest {

    @Test
    fun onPrepared() {
        val event = Prepared("to", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<Entry> {
                assertThat(it.id, equalTo(event.to))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }

    @Test
    fun onAmountEntered() {
        val event = AmountEntered("sourceId", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<Confirm> {
                assertThat(it.id, equalTo(event.sourceId))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }

    @Test
    fun onEntryFailed() {
        val event = EntryFailed("sourceId", 1)
        sagaVerifier<TransferSaga>()
            .`when`(event)
            .expectCommandBody<UnlockAmount> {
                assertThat(it.id, equalTo(event.sourceId))
                assertThat(it.amount, equalTo(event.amount))
            }
            .verify()
    }
}

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=336

分享给朋友:

“范畴驱动规划之银行转账:Wow结构实战” 的相关文章

PLC结构化文本(ST)——承继(inheritance)

PLC结构化文本(ST)——承继(inheritance)

PLC Structured Text Object Oriented Programming PLC结构化文本(ST)——承继(inheritance) 承继的概念 承继是面向目标程序规划中最重要的概念之一。承继答应咱们依据一个类来界说另一个类,这使得创立和保护应用程序变得更简单。一起也有利于重...

读软件设计的要素01概念

读软件设计的要素01概念

1. 概念 1.1. 一个软件,从运转于手机上的最小程序到大型的企业体系,都是由概念组成的,每个概念都是独立的功用单元 1.2. 软件中的可用性问题,常常能够追溯到其底层概念 1.2.1. 概念协助辨认软件的不可用性 1.3. 概念都是以相同办法在各种软件中重复运用的 1.3.1. 将规划分解为最...

从混乱到高雅:根据DDD的六边形架构的代码创新攻略

从混乱到高雅:根据DDD的六边形架构的代码创新攻略

前语 趁着双十一备战封板,总算又有一些时刻可以整理一下最近的心得。 最近这半年跟搭档评论比较多的是分层架构,然后就会遇到两个触及魂灵的问题,一个是怎么做好分层架构,二是DDD在架构层面该怎么落地。 为了说好分层,咱们需求了解架构的含义。 杰出的架构是为了确保一下两点: 办理运用杂乱度,下降体系熵值;...

面向对象程序设计教程,入门与进阶

面向对象程序设计教程,入门与进阶

Python 面向对象编程1. CSDN博客 提供了详细的Python面向对象编程教程,包括类的详解和实战案例。 qwe22. 廖雪峰的官方网站 提供了Python面向对象编程的详细教程,适合入门和高级学习者。 3. B站视频 浙江大学的翁恺教授讲解了面向对象设计C ,适合对C...

前端架构设计,构建高效、可维护的现代Web应用

前端架构设计,构建高效、可维护的现代Web应用

1. 组件化:将页面拆分成独立的、可复用的组件,每个组件负责一部分功能。这样可以提高代码的可维护性和可复用性。2. 模块化:将代码拆分成独立的、可管理的模块,每个模块负责一部分功能。这样可以提高代码的可维护性和可扩展性。3. 状态管理:设计一个合理的状态管理方案,以管理应用程序的状态。这可以是一个全...

商业模式的设计,构建可持续发展的企业蓝图

1. 市场分析:了解目标市场的需求、竞争状况、消费者行为等。这有助于确定你的产品或服务在市场中的定位。2. 价值主张:明确你的产品或服务能为客户提供的独特价值。这应该与市场需求相匹配,并能够解决客户的问题或满足他们的需求。3. 成本结构:分析提供产品或服务所需的成本,包括原材料、生产、营销、运营等成...