Slide 1

Slide 1 text

GoͱΞΫλʔϞσϧͰ ES+CQRSΛ࣮ફ SEN Developer Meetup vol.2

Slide 2

Slide 2 text

Pro fi le • ஛ᖒ ༗و a.k.a ytake • ઍגࣜձࣾ CTO / ΄͔ٕज़ސ໰ • Go / Scala • ΞΫλʔϞσϧେ޷͖

Slide 3

Slide 3 text

GoͰຊ֨తͳΞΫλʔϞσϧʂ

Slide 4

Slide 4 text

݁ہͷͱ͜Ζ • ΞΫλʔϞσϧ͸ϓϩάϥϛϯάύϥμΠϜ͕ͪΐͬͱҧ͏΋ͷ • ฉ͚ͩ͘Ͱ͸ͳ͔ͳ͔೉͍͠ͷͰɺͥͻ৮ͬͯΈ͍ͯͩ͘͞ • εςʔτϑϧʹ

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Proto.Actor • Akka / Pekkoͱڞ௨ͷ֓೦ʹجͮ͘΋ͷ • Akka / Pekko΄ͲߴػೳͰ͸ͳ͍͕ɺෆࣗ༝͕ͳ͍͘Β͍ἧ͍ͬͯΔ • ࠓճ͸ӬଓԽ౳ʹؔ͢Δ΋ͷ

Slide 7

Slide 7 text

ΞΫλʔϞσϧʹ͍ͭͯཧղ͢ΔͨΊʹ

Slide 8

Slide 8 text

ΑΓཧղ͢ΔͨΊʹ

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

؆୯ʹ֓೦঺հ

Slide 11

Slide 11 text

ΞΫλʔϞσϧ • ฒߦܭࢉͷͨΊͷϞσϧ • ఻౷తͳϓϩάϥϛϯάελΠϧͱଟগ͜ͱͳΓɺ ͢΂ͯͷΞΫλʔ͸ಠཱͯ͠ฒߦ • ΦϒδΣΫτͷ֎͔Β௚઀ίʔϧͯ͠ґཔ͢ΔͳͲ͸Ͱ͖·ͤΜ

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

֊૚ߏ଄Λ΋ͭ

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

ES+CQRSʹ͍ͭͯ

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Command • γεςϜ΁ͷมߋࢦࣔ • ίϚϯυ͕࣮ߦ͞ΕΔͱɺγεςϜ͕৽͍͠ঢ়ଶʹมԽ

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Query • ϢʔβʔʹσʔλΛදࣔͨ͠Γɺ • ผͷγεςϜʹૹ৴ͨ͠Γɺ ίϚϯυͱ͸ผͷ໨తͰ࢖༻ • ίϚϯυͱ͸Ϟσϧ͕ผͱ͍͏͜ͱ

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

Event Store? • ίϚϯυΛड͚ͯ৽͍͠ঢ়ଶʹมԽ • ͜ΕΛΠϕϯτͱͯ͠อ؅ʢΠϕϯτετΞ΁ʣ • ΠϕϯτετΞ͸௚઀ΫΤϦʹར༻͞ΕΔΘ͚Ͱ͸ͳ͍

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

ӬଓԽ͢Δʹ͸ • Protocol BuffersΛར༻͢Δ • ΠϕϯτετΞʹ͍ͭͯ͸ΠϯλʔϑΣʔεΛ࣮૷ • ΠϕϯτͷӬଓԽΛߦ͍ɺ֘౰ͷΞΫλʔͷঢ়ଶΛ؅ཧ • ঢ়ଶ෮ݩ͸ࣗಈͰ࣮ߦ͞Ε·͢

Slide 26

Slide 26 text

package persistence import ( "google.golang.org/protobuf/proto" ) // Provider is the abstraction used for persistence type Provider interface { GetState() ProviderState } // ProviderState is an object containing the implementation for the provider type ProviderState interface { SnapshotStore EventStore Restart() GetSnapshotInterval() int } type SnapshotStore interface { GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) PersistSnapshot(actorName string, snapshotIndex int, snapshot proto.Message) DeleteSnapshots(actorName string, inclusiveToIndex int) } type EventStore interface { GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{})) PersistEvent(actorName string, eventIndex int, event proto.Message) DeleteEvents(actorName string, inclusiveToIndex int) }

Slide 27

Slide 27 text

package persistence import ( "google.golang.org/protobuf/proto" ) // Provider is the abstraction used for persistence type Provider interface { GetState() ProviderState } // ProviderState is an object containing the implementation for the provider type ProviderState interface { SnapshotStore EventStore Restart() GetSnapshotInterval() int } type SnapshotStore interface { GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) PersistSnapshot(actorName string, snapshotIndex int, snapshot proto.Message) DeleteSnapshots(actorName string, inclusiveToIndex int) } type EventStore interface { GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{})) PersistEvent(actorName string, eventIndex int, event proto.Message) DeleteEvents(actorName string, inclusiveToIndex int) } ঢ়ଶͷӬଓԽʹͳʹΛ࢖͏͔

Slide 28

Slide 28 text

package persistence import ( "google.golang.org/protobuf/proto" ) // Provider is the abstraction used for persistence type Provider interface { GetState() ProviderState } // ProviderState is an object containing the implementation for the provider type ProviderState interface { SnapshotStore EventStore Restart() GetSnapshotInterval() int } type SnapshotStore interface { GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) PersistSnapshot(actorName string, snapshotIndex int, snapshot proto.Message) DeleteSnapshots(actorName string, inclusiveToIndex int) } type EventStore interface { GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{})) PersistEvent(actorName string, eventIndex int, event proto.Message) DeleteEvents(actorName string, inclusiveToIndex int) } ΠϕϯτετΞʢδϟʔφϧʣ޲͚ ӬଓԽͷํ๏ʢΫΤϦʣ΍ ෮ݩ࣌ͷΫΤϦͳͲ

Slide 29

Slide 29 text

package persistence import ( "google.golang.org/protobuf/proto" ) // Provider is the abstraction used for persistence type Provider interface { GetState() ProviderState } // ProviderState is an object containing the implementation for the provider type ProviderState interface { SnapshotStore EventStore Restart() GetSnapshotInterval() int } type SnapshotStore interface { GetSnapshot(actorName string) (snapshot interface{}, eventIndex int, ok bool) PersistSnapshot(actorName string, snapshotIndex int, snapshot proto.Message) DeleteSnapshots(actorName string, inclusiveToIndex int) } type EventStore interface { GetEvents(actorName string, eventIndexStart int, eventIndexEnd int, callback func(e interface{})) PersistEvent(actorName string, eventIndex int, event proto.Message) DeleteEvents(actorName string, inclusiveToIndex int) } εφοϓγϣοτ޲͚ ӬଓԽͷํ๏ʢΫΤϦʣ΍ ෮ݩ࣌ͷΫΤϦͳͲ

Slide 30

Slide 30 text

ӬଓԽ • δϟʔφϧ͸ঢ়ଶมߋΛৗʹهԱ • δϟʔφϧΛ͍͔ͭ͘ॻ͖ࠐΉͨͼʹεφοϓγϣοτΛอ؅ • ෮ݩ͸಄͔Β࣮ߦ͞ΕΔ͕ɺεφοϓγϣοτ͕͋Ε͹͔ͦ͜Β෮ݩ

Slide 31

Slide 31 text

type Actor interface { Receive(c Context) }

Slide 32

Slide 32 text

package acme import ( "errors" "fmt" "github.com/asynkron/protoactor-go/actor" "github.com/asynkron/protoactor-go/persistence" // ུ "google.golang.org/protobuf/proto" ) // Cart is an actor to create cart type Cart struct { persistence.Mixin stream *actor.PID state *cart.Items }

Slide 33

Slide 33 text

package acme import ( "errors" "fmt" "github.com/asynkron/protoactor-go/actor" "github.com/asynkron/protoactor-go/persistence" // ུ "google.golang.org/protobuf/proto" ) // Cart is an actor to create cart type Cart struct { persistence.Mixin stream *actor.PID state *cart.Items } ΞΫλʔͷӬଓԽΛར༻͢Δ৔߹ʹ ඞਢ

Slide 34

Slide 34 text

package acme import ( "errors" "fmt" "github.com/asynkron/protoactor-go/actor" "github.com/asynkron/protoactor-go/persistence" // ུ "google.golang.org/protobuf/proto" ) // Cart is an actor to create cart type Cart struct { persistence.Mixin stream *actor.PID state *cart.Items } $BSUΞΫλʔͷঢ়ଶ

Slide 35

Slide 35 text

package acme import ( "errors" "fmt" "github.com/asynkron/protoactor-go/actor" "github.com/asynkron/protoactor-go/persistence" // ུ "google.golang.org/protobuf/proto" ) // Cart is an actor to create cart type Cart struct { persistence.Mixin stream *actor.PID state *cart.Items } ϦʔυϞσϧߋ৽ΞΫλʔͳͲ΁

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

ΞΫλʔͷঢ়ଶΛมߋ υϝΠϯϞσϧʹ߹Θͤͯදݱ

Slide 39

Slide 39 text

ΞΫλʔͷঢ়ଶΛมߋ υϝΠϯΠϕϯτ

Slide 40

Slide 40 text

ΠϕϯτετΞͰ ΠϕϯτӬଓԽ

Slide 41

Slide 41 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Name) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } }

Slide 42

Slide 42 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Email) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } } ίϚϯυΛडऔɺ ࣗΞΫλʔͷঢ়ଶΛมߋ ঢ়ଶมߋΛΠϕϯτͱͯ͠ӬଓԽ

Slide 43

Slide 43 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Name) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } } ͢Ͱʹঢ়ଶ͕͋Δʢੜ੒ࡁΈͳͲʣ৔߹͸ ࣗ਎Λมߋͤͣʹϝοηʔδฦ৴

Slide 44

Slide 44 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Name) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } } ϦʔυϞσϧߋ৽ϋϯυϥʢΞΫλʔʣ΁

Slide 45

Slide 45 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Name) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } } ॲཧऴྃޙʹΞΫλʔΛ࡟আ

Slide 46

Slide 46 text

func (u *Cart) Receive(context actor.Context) { defer context.Poison(context.Self()) switch msg := context.Message().(type) { case *persistence.RequestSnapshot: u.PersistSnapshot(u.state) case *persistence.ReplayComplete: // ϦϓϨΠ͕׬ྃͨ͠Β಺෦ঢ়ଶΛมߋ͢Δ context.Logger().Info( fmt.Sprintf("replay completed, internal state changed to '%v'", u.state)) case *command.AddItem: if u.IsStateExists(msg.Name) { context.Send(u.stream, &message.AddItemError{Message: "item already exists"}) return } // ུ u.persistence(context, ev) // xxxੜ੒ΠϕϯτΛΠϕϯτετϦʔϜ΁ context.Send(u.stream, ev) case *event.ItemAdded: if msg.String() != "" { // event ͕ϦϓϨΠ͞Εͨ৔߹͸ঢ়ଶΛߋ৽͢Δ u.state = msg u.sendToReadModelUpdater(context, msg) } } } ΞΫλʔੜ੒ʢ࠶ੜ੒ɺϦελʔτͳͲʣ࣌ աڈͷঢ়ଶ͕͋Ε͹ɺ ࠷ॳͷΠϕϯτ͔Βड৴͠ɺঢ়ଶΛ෮ݩ

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

ϦʔυϞσϧߋ৽ΞΫλʔ͕ 2VFSZ༻ͷσʔλϕʔεΛߋ৽

Slide 49

Slide 49 text

// ReadModelUpdate is actor to update read model type ReadModelUpdate struct { query mysql.RegistrationItemExecutor } // NewReadModelUpdate is constructor for ReadModelUpdate func NewReadModelUpdate(query mysql.RegistrationItemExecutor) actor.Actor { return &ReadModelUpdate{ query: query, } } // Receive is sent messages to be processed from the mailbox associated with the instance of the actor func (u *ReadModelUpdate) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *event.ItemAdded: // ΠϕϯτΛಡΈࠐΜͰɺRead ModelΛߋ৽͢Δ // ͜͜Ͱ͸Read ModelʹΞΠςϜ͕ଘࡏ͠ͳ͍৔߹ʹ࡞੒͢Δ෩ err := u.query.AddItemIfNotExists(context.Background(), mysql.AddItemParams{ Name: msg.ItemName, ID: msg.ItemID, }) if err != nil { // Τϥʔ͕ൃੜͨ͠৔߹͸ϩάΛग़ྗ͢Δ ctx.Logger().Error(err.Error()) return } } }

Slide 50

Slide 50 text

// ReadModelUpdate is actor to update read model type ReadModelUpdate struct { query mysql.RegistrationItemExecutor } // NewReadModelUpdate is constructor for ReadModelUpdate func NewReadModelUpdate(query mysql.RegistrationItemExecutor) actor.Actor { return &ReadModelUpdate{ query: query, } } // Receive is sent messages to be processed from the mailbox associated with the instance of the actor func (u *ReadModelUpdate) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *event.ItemAdded: // ΠϕϯτΛಡΈࠐΜͰɺRead ModelΛߋ৽͢Δ // ͜͜Ͱ͸Read ModelʹΞΠςϜ͕ଘࡏ͠ͳ͍৔߹ʹ࡞੒͢Δ෩ err := u.query.AddItemIfNotExists(context.Background(), mysql.AddItemParams{ Name: msg.ItemName, ID: msg.ItemID, }) if err != nil { // Τϥʔ͕ൃੜͨ͠৔߹͸ϩάΛग़ྗ͢Δ ctx.Logger().Error(err.Error()) return } } } 2VFSZͰར༻͍ͨ͠ςʔϒϧͳͲʹ൓ө

Slide 51

Slide 51 text

ΞΫλʔΛੜ੒ • ͜ͷྫͰ͸RestAPIΞΫλʔΛϧʔτʹ • ίϚϯυ͔Βঢ়ଶมԽɺ ΫΤϦͰඞཁͳςʔϒϧͷՃ޻ʢϦʔυϞσϧߋ৽ϋϯυϥʣ ͦΕͧΕͷΞΫλʔΛੜ੒

Slide 52

Slide 52 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } }

Slide 53

Slide 53 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } } ϦʔυϞσϧߋ৽ΞΫλʔ

Slide 54

Slide 54 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } } ίϚϯυΛड͚෇͚ΔΞΫλʔ

Slide 55

Slide 55 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } } ΞΫλʔͷӬଓԽࢦࣔ 1SPUP"DUPSͰ͸ϛυϧ΢ΣΞͱͯ͠දݱ

Slide 56

Slide 56 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } } ಠཱͨ͠ΞΫλʔʹࣝผՄೳͳ໊લΛ༩͑ɺ ෮ݩ࣌ʹಠཱͯ͠෮ݩ͞ΕΔ

Slide 57

Slide 57 text

func (a *RestAPI) Receive(ctx actor.Context) { switch msg := ctx.Message().(type) { case *actor.Started: a.rmu = ctx.Spawn(actor.PropsFromProducer(func() actor.Actor { return registration.NewItemModelUpdate(a.db) })) case *command.AddItem: ref, err := ctx.SpawnNamed( actor.PropsFromProducer(func() actor.Actor { return registration.NewItem(msg.Stream, a.rmu) }, actor.WithReceiverMiddleware(persistence.Using(a.provider))), "item-"+msg.Name) if errors.Is(err, actor.ErrNameExists) { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("item %s already exists", msg.Email)}) return } if err != nil { ctx.Send(msg.Stream, &message.ItemCreateError{Message: fmt.Sprintf("failed error %s", err.Error())}) return } ctx.Send(ref, msg) } } ΞΫλʔ͕ੜ੒Ͱ͖ͳ͔ͬͨ৔߹ͷΤϥʔ

Slide 58

Slide 58 text

ΞΫλʔγεςϜ͕શମΛ؅ཧ ো֐ͳͲʹ΋ڧ͘ɺ ಄͔Β࣮֬ʹ෮چ

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

func (u *ItemRegistration) Handle(c echo.Context) error { ui := new(itemInput) if err := c.Bind(ui); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(ui); err != nil { return err } go func() { u.system.Root.Send(u.ref, &command.AddItem{ // ུ Name: ui.Name, Stream: u.stream.PID(), }) }() res := <-u.stream.C() if res.IsSuccess() { return c.JSON(http.StatusOK, res) } return c.JSON(http.StatusBadRequest, res) }

Slide 61

Slide 61 text

func (u *ItemRegistration) Handle(c echo.Context) error { ui := new(itemInput) if err := c.Bind(ui); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(ui); err != nil { return err } go func() { u.system.Root.Send(u.ref, &command.AddItem{ // ུ Name: ui.Name, Stream: u.stream.PID(), }) }() res := <-u.stream.C() if res.IsSuccess() { return c.JSON(http.StatusOK, res) } return c.JSON(http.StatusBadRequest, res) } 1SPUP"DUPS΁ૹ৴

Slide 62

Slide 62 text

func (u *ItemRegistration) Handle(c echo.Context) error { ui := new(itemInput) if err := c.Bind(ui); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(ui); err != nil { return err } go func() { u.system.Root.Send(u.ref, &command.AddItem{ // ུ Name: ui.Name, Stream: u.stream.PID(), }) }() res := <-u.stream.C() if res.IsSuccess() { return c.JSON(http.StatusOK, res) } return c.JSON(http.StatusBadRequest, res) } ΄͍͠ϝοηʔδΛετϦʔϜ͔ΒऔΓग़͢ ଞʹ΋'VUVSFΛ࢖͏ͳͲ΋͋Γ·͢

Slide 63

Slide 63 text

·ͱΊ • ΞΫλʔϞσϧ͸ϓϩάϥϛϯάύϥμΠϜ͕ͪΐͬͱҧ͏΋ͷ • ฉ͚ͩ͘Ͱ͸ͳ͔ͳ͔೉͍͠ͷͰɺͥͻ৮ͬͯΈ͍ͯͩ͘͞ • ཧղ͑͞Ͱ͖Ε͹ڧྗͳπʔϧʹʂ