Skip to content

Instantly share code, notes, and snippets.

View emag's full-sized avatar

Yoshimasa Tanabe emag

View GitHub Profile
@emag
emag / Main.scala
Created December 9, 2024 00:33
家計簿アプリケーションの実行
package kakeibo
import cats.effect.{IO, IOApp}
import edomata.core.CommandMessage
import kakeibo.JournalEntry.AccountTitle.*
import java.time.{Instant, LocalDate}
object Main extends IOApp.Simple {
@emag
emag / Application.scala
Last active December 9, 2024 00:31
家計簿アプリケーション
package kakeibo
import cats.effect.Async
import cats.effect.kernel.Resource
import cats.effect.std.Console
import fs2.io.net.Network
import natchez.Trace.Implicits.noop
import skunk.Session
final case class Application[F[_]](
@emag
emag / BalanceSheetApp.scala
Created December 9, 2024 00:15
バックエンドの定義
object BalanceSheetApp {
given BackendCodec[BsEvent] = CirceCodec.json
given BackendCodec[BsNotification] = CirceCodec.json
given BackendCodec[BalanceSheet] = CirceCodec.json
def backend[F[_]: Async](pool: Resource[F, Session[F]]) = Backend
.builder(BalanceSheetService)
.use(SkunkDriver("edomata_kakeibo", pool))
.build
@emag
emag / BalanceSheetServiceSpec.scala
Last active December 8, 2024 14:51
サービスのテスト
import cats.Id
"初期化" should {
"コマンドを受け付ける" in {
// Given
val initA = Map(Cash -> BigDecimal(1_000_000))
val initL = Map(AccruedLiability -> BigDecimal(100_000))
val initializeScenario = RequestContext(
command = CommandMessage(
id = "some-id",
@emag
emag / BalanceSheetService.scala
Created December 8, 2024 14:08
BS サービス
object BalanceSheetService
extends BalanceSheet.Service[BsCommand, BsNotification] {
def apply[F[_]: Monad]: App[F, Unit] = App.router {
case BsCommand.Initialize(a, l) =>
for {
ready <- App.state.decide(_.initialize(a, l))
aggId <- App.aggregateId
_ <- App.publish(BsNotification.BalanceSheetPrepared(aggId, ready))
} yield ()
case BsCommand.Journalize(e) =>
@emag
emag / BalanceSheetSpec.scala
Created December 8, 2024 13:20
仕訳の記帳が拒否されるパターン
"負数の残高にはできない" in {
// Given
val initA = Map(Cash -> BigDecimal(1_000_000))
val initL = Map.empty[AccountTitle, BigDecimal]
val entry = JournalEntry(
date = LocalDate.now(),
debit = BankAccount,
credit = Cash,
amount = BigDecimal(2_000_000),
description = "入金"
@emag
emag / BalanceSheetSpec.scala
Created December 8, 2024 13:01
仕訳の記帳
"仕訳の記帳" should {
"BS を更新する" in {
// Given
val initA = Map(Cash -> BigDecimal(1_000_000))
val initL = Map(AccruedLiability -> BigDecimal(100_000))
val entry1 = JournalEntry(
date = LocalDate.now(),
debit = BankAccount,
credit = Cash,
amount = BigDecimal(500_000),
@emag
emag / BalanceSheetSpec.scala
Created December 8, 2024 10:26
初期化の動作確認
"初期化" should {
"任意の資産と負債を受け取り、準備ができる" in {
// Given
val initialAssets = ...
val initialLiabilities = ...
// When
val result = BalanceSheet.NotReady.initialize(
assets = initialAssets,
liabilities = initialLiabilities
)
@emag
emag / BalanceSheet.scala
Last active December 8, 2024 10:11
BS 集約の状態更新
object BalanceSheet extends DomainModel[BalanceSheet, BsEvent, BsRejection] {
override def initial: BalanceSheet = NotReady
override def transition
: BsEvent => BalanceSheet => ValidatedNec[BsRejection, BalanceSheet] = {
case Initialized(as, ls) => _ => Ready(as, ls).validNec
case Journalized(e) =>
_.mustBeReady.map { bs =>
bs.copy(
@emag
emag / BalanceSheet.scala
Last active December 8, 2024 09:31
BS 集約 + ドメインロジック
import edomata.core.*
import edomata.syntax.all.*
enum BalanceSheet {
case NotReady
case Ready(
assets: Map[AccountTitle, BigDecimal],
liabilities: Map[AccountTitle, BigDecimal]
)