johanhaleby / occurrent

Unintrusive Event Sourcing Library for the JVM
https://occurrent.org
120 stars 16 forks source link

Commands for ModuleDSL #110

Open johanhaleby opened 2 years ago

johanhaleby commented 2 years ago

One idea would be to force the use of commands. This would allow one to do:

module {
    commands { c -> when(c) {
            is SomeCommand1 -> SomeDomainModel.handle(c.id, c)
            is SomeCommand2 -> SomeDomainModel.handle(c.id, c)
        }
    }
}

This would also work if the DM doesn't use commands:

module {
    commands { c -> when(c) {
            is SomeCommand1 -> SomeDomainModel.handle(c.id, c.thing1, c.thing2)
            is SomeCommand2 -> SomeDomainModel.handle(c.id, c.thing3)
        }
    }
}

It would also be possible to do:

module {
    commands(MyCommand::id, SomeDomainModle::handle)
}

if the DM handles all command types. Or if you have two different commands handlers (aggregates):

module {
    commands<GameCommand>(GameCommand::gameId, Game::handle)
    commands<PlayerCommand>(PlayerCommand::playerId, Player::handle)
}

Alternatively, if commands should only be defined once:

module {
        commands {
            command<GameCommand>(GameCommand::gameId, Game::handle)
            command<PlayerCommand>(PlayerCommand::playerId, Player::handle)
        }
}

An alternative. that is probably better, would be to pass the "aggregate id finder functions" to commands:

fun findId(cmd : Command) = when(cmd) {
    is GameCommand -> cmd.gameId
    is PlayerCommand -> cmd.playerId
}

module {
        commands(::findId) {
            command<GameCommand>(Game::handle)
            command<PlayerCommand>(Player::handle)
        }
}

Or maybe a combination? I.e. that you can pass "aggregate id finder functions" both to commands and command where command has precedence.

Note that module DSL should be lazy! Don't use subscription DSL etc directly in the module! (and interface would be ok!)

You compose modules into an Application, e.g.:

interface AllStartedGamesQuery : Query<AllStartedGamesDTO>

val module1 = module { .. } 
val module2 = module { .. } 

val applicationService = ..
val subscriptionDsl = ..
val domainQueryDsl = 

val application = application(module1, module2).start(applicationService, subscriptionDsl, domainQueryDsl)

application.dispatch(PlayGameCommand("gameId", Shape.Hand))
val allStartedGames = application.query<AllStartedGamesQuery>()
johanhaleby commented 2 years ago

How to handle integration events????