Базовая модель Factoriat выглядит так:
Factor -> Predicates -> Rules -> Facts -> Apply
Для stateless-сценария поток ещё проще:
Factor -> Build -> []Fact
Для stateful-сценария используется полный lifecycle:
Factor -> Capture -> Evaluate -> Build -> Stabilize -> Emit
Factor — это входной снимок состояния. Он содержит данные, нужные для решения:
type Factor struct {
PlayerID string
HasSeat bool
Folded bool
State player.State
Now time.Time
LastSeen time.Time
}В Factor не нужно класть готовые решения вроде ShouldLeave или CanDelete. Лучше передавать исходные данные, а решение пусть принимает факториат.
Предикаты дают имена условиям:
func (f Factor) nonFoldedSitOut() bool {
return f.HasSeat && !f.Folded && f.State == player.SitOut
}После этого rules builder читает доменный смысл, а не boolean-проводку.
Fact — это решение. Он говорит, что должно произойти, но сам ничего не делает:
type SitOutPlayerShouldMarkLeavePending struct {
PlayerID string
}Факты применяются снаружи:
for _, fact := range facts {
switch f := fact.(type) {
case SitOutPlayerShouldMarkLeavePending:
player.MarkLeavePending(f.PlayerID)
}
}Так факториат остаётся чистым, а side effects выполняются в нужном application или domain layer.