func Example() { // Support Wercker testing with MongoDB. host := os.Getenv("MONGO_PORT_27017_TCP_ADDR") port := os.Getenv("MONGO_PORT_27017_TCP_PORT") url := "localhost" if host != "" && port != "" { url = host + ":" + port } // Create the event store. eventStore, err := eventstore.NewEventStore(url, "demo") if err != nil { log.Fatalf("could not create event store: %s", err) } // Create the event bus that distributes events. eventBus := eventbus.NewEventBus() eventBus.AddObserver(&domain.Logger{}) // Create the aggregate repository. repository, err := eh.NewEventSourcingRepository(eventStore, eventBus) if err != nil { log.Fatalf("could not create repository: %s", err) } // Create the aggregate command handler. handler, err := eh.NewAggregateCommandHandler(repository) if err != nil { log.Fatalf("could not create command handler: %s", err) } // Register the domain aggregates with the dispather. Remember to check for // errors here in a real app! handler.SetAggregate(domain.InvitationAggregateType, domain.CreateInviteCommand) handler.SetAggregate(domain.InvitationAggregateType, domain.AcceptInviteCommand) handler.SetAggregate(domain.InvitationAggregateType, domain.DeclineInviteCommand) // Create the command bus and register the handler for the commands. commandBus := commandbus.NewCommandBus() commandBus.SetHandler(handler, domain.CreateInviteCommand) commandBus.SetHandler(handler, domain.AcceptInviteCommand) commandBus.SetHandler(handler, domain.DeclineInviteCommand) // Create and register a read model for individual invitations. invitationRepository, err := readrepository.NewReadRepository(url, "demo", "invitations") if err != nil { log.Fatalf("could not create invitation repository: %s", err) } invitationRepository.SetModel(func() interface{} { return &domain.Invitation{} }) invitationProjector := domain.NewInvitationProjector(invitationRepository) eventBus.AddHandler(invitationProjector, domain.InviteCreatedEvent) eventBus.AddHandler(invitationProjector, domain.InviteAcceptedEvent) eventBus.AddHandler(invitationProjector, domain.InviteDeclinedEvent) // Create and register a read model for a guest list. eventID := eh.NewUUID() guestListRepository, err := readrepository.NewReadRepository(url, "demo", "guest_lists") if err != nil { log.Fatalf("could not create guest list repository: %s", err) } guestListRepository.SetModel(func() interface{} { return &domain.GuestList{} }) guestListProjector := domain.NewGuestListProjector(guestListRepository, eventID) eventBus.AddHandler(guestListProjector, domain.InviteCreatedEvent) eventBus.AddHandler(guestListProjector, domain.InviteAcceptedEvent) eventBus.AddHandler(guestListProjector, domain.InviteDeclinedEvent) // Clear DB collections. eventStore.Clear() invitationRepository.Clear() guestListRepository.Clear() // Issue some invitations and responses. // Note that Athena tries to decline the event, but that is not allowed // by the domain logic in InvitationAggregate. The result is that she is // still accepted. athenaID := eh.NewUUID() commandBus.HandleCommand(&domain.CreateInvite{InvitationID: athenaID, Name: "Athena", Age: 42}) commandBus.HandleCommand(&domain.AcceptInvite{InvitationID: athenaID}) err = commandBus.HandleCommand(&domain.DeclineInvite{InvitationID: athenaID}) if err != nil { log.Printf("error: %s\n", err) } hadesID := eh.NewUUID() commandBus.HandleCommand(&domain.CreateInvite{InvitationID: hadesID, Name: "Hades"}) commandBus.HandleCommand(&domain.AcceptInvite{InvitationID: hadesID}) zeusID := eh.NewUUID() commandBus.HandleCommand(&domain.CreateInvite{InvitationID: zeusID, Name: "Zeus"}) commandBus.HandleCommand(&domain.DeclineInvite{InvitationID: zeusID}) // Read all invites. invitations, _ := invitationRepository.FindAll() for _, i := range invitations { if i, ok := i.(*domain.Invitation); ok { log.Printf("invitation: %s - %s\n", i.Name, i.Status) fmt.Printf("invitation: %s - %s\n", i.Name, i.Status) } } // Read the guest list. l, _ := guestListRepository.Find(eventID) if l, ok := l.(*domain.GuestList); ok { log.Printf("guest list: %d guests (%d accepted, %d declined)\n", l.NumGuests, l.NumAccepted, l.NumDeclined) fmt.Printf("guest list: %d guests (%d accepted, %d declined)\n", l.NumGuests, l.NumAccepted, l.NumDeclined) } // records := eventStore.FindAllEventRecords() // fmt.Printf("event records:\n") // for _, r := range records { // fmt.Printf("%#v\n", r) // } // Output: // invitation: Athena - accepted // invitation: Hades - accepted // invitation: Zeus - declined // guest list: 3 guests (2 accepted, 1 declined) }
// Run runs the test scenario with the given EventStore and CommandBus. // EventStores and ReadRepositories are created as needed with the given functions. func Run(eventBus eventhorizon.EventBus, commandBus eventhorizon.CommandBus, newEventStore NewEventStoreFunc, newReadRepository NewReadRepositoryFunc) { eventBus.AddGlobalHandler(&loggerSubscriber{}) if remoteEventBus, ok := eventBus.(eventhorizon.RemoteEventBus); ok { fmt.Println("event registered") remoteEventBus.RegisterEventType(&InviteCreated{}, func() eventhorizon.Event { return &InviteCreated{} }) remoteEventBus.RegisterEventType(&InviteAccepted{}, func() eventhorizon.Event { return &InviteAccepted{} }) remoteEventBus.RegisterEventType(&InviteDeclined{}, func() eventhorizon.Event { return &InviteDeclined{} }) defer remoteEventBus.Close() } if remoteCommandBus, ok := commandBus.(eventhorizon.RemoteCommandBus); ok { fmt.Println("command registered") remoteCommandBus.RegisterCommandType(&CreateInvite{}, func() eventhorizon.Command { return &CreateInvite{} }) remoteCommandBus.RegisterCommandType(&AcceptInvite{}, func() eventhorizon.Command { return &AcceptInvite{} }) remoteCommandBus.RegisterCommandType(&DeclineInvite{}, func() eventhorizon.Command { return &DeclineInvite{} }) defer remoteCommandBus.Close() } // Create the event store. eventStore, err := newEventStore() if err != nil { log.Fatalf("could not create event store: %s", err) } if remoteEventStore, ok := eventStore.(eventhorizon.RemoteEventStore); ok { remoteEventStore.RegisterEventType(&InviteCreated{}, func() eventhorizon.Event { return &InviteCreated{} }) remoteEventStore.RegisterEventType(&InviteAccepted{}, func() eventhorizon.Event { return &InviteAccepted{} }) remoteEventStore.RegisterEventType(&InviteDeclined{}, func() eventhorizon.Event { return &InviteDeclined{} }) remoteEventStore.Clear() defer remoteEventStore.Close() } // Create the aggregate repository. repository, err := eventhorizon.NewCallbackRepository(eventStore) if err != nil { log.Fatalf("could not create repository: %s", err) } // Register an aggregate factory. repository.RegisterAggregate(&InvitationAggregate{}, func(id string) eventhorizon.Aggregate { return &InvitationAggregate{ AggregateBase: eventhorizon.NewAggregateBase(id), } }, ) // Create the aggregate command handler. handler, err := eventhorizon.NewAggregateCommandHandler(repository) if err != nil { log.Fatalf("could not create command handler: %s", err) } // Register the domain aggregates with the dispather. Remember to check for // errors here in a real app! handler.SetAggregate(&InvitationAggregate{}, &CreateInvite{}) handler.SetAggregate(&InvitationAggregate{}, &AcceptInvite{}) handler.SetAggregate(&InvitationAggregate{}, &DeclineInvite{}) // Create the command bus and register the handler for the commands. commandBus.SetHandler(handler, &CreateInvite{}) commandBus.SetHandler(handler, &AcceptInvite{}) commandBus.SetHandler(handler, &DeclineInvite{}) // Create and register a read model for individual invitations. invitationRepository, err := newReadRepository("invitations") if err != nil { log.Fatalf("could not create invitation repository: %s", err) } if remoteRepository, ok := invitationRepository.(eventhorizon.RemoteReadRepository); ok { remoteRepository.SetModel(func() interface{} { return &Invitation{} }) remoteRepository.Clear() defer remoteRepository.Close() } invitationProjector := NewInvitationProjector(invitationRepository) eventBus.AddHandler(invitationProjector, &InviteCreated{}) eventBus.AddHandler(invitationProjector, &InviteAccepted{}) eventBus.AddHandler(invitationProjector, &InviteDeclined{}) // Create and register a read model for a guest list. eventID := uuid.New() guestListRepository, err := newReadRepository("guest_lists") if err != nil { log.Fatalf("could not create guest list repository: %s", err) } if remoteRepository, ok := guestListRepository.(eventhorizon.RemoteReadRepository); ok { remoteRepository.SetModel(func() interface{} { return &GuestList{} }) remoteRepository.Clear() defer remoteRepository.Close() } guestListProjector := NewGuestListProjector(guestListRepository, eventID) eventBus.AddHandler(guestListProjector, &InviteCreated{}) eventBus.AddHandler(guestListProjector, &InviteAccepted{}) eventBus.AddHandler(guestListProjector, &InviteDeclined{}) // Issue some invitations and responses. // Note that Athena tries to decline the event, but that is not allowed // by the domain logic in InvitationAggregate. The result is that she is // still accepted. athenaID := uuid.New() commandBus.PublishCommand(&CreateInvite{InvitationID: athenaID, EventID: eventID, Name: "Athena", Age: 42}) commandBus.PublishCommand(&AcceptInvite{InvitationID: athenaID}) err = commandBus.PublishCommand(&DeclineInvite{InvitationID: athenaID}) if err != nil { fmt.Printf("error: %s\n", err) } hadesID := uuid.New() commandBus.PublishCommand(&CreateInvite{InvitationID: hadesID, EventID: eventID, Name: "Hades"}) commandBus.PublishCommand(&AcceptInvite{InvitationID: hadesID}) zeusID := uuid.New() commandBus.PublishCommand(&CreateInvite{InvitationID: zeusID, EventID: eventID, Name: "Zeus"}) commandBus.PublishCommand(&DeclineInvite{InvitationID: zeusID}) // TODO: Find a better way to ensure that all events were processed time.Sleep(1 * time.Second) // Read all invites. invitations, _ := invitationRepository.FindAll() for _, i := range invitations { fmt.Printf("invitation: %#v\n", i) } // Read the guest list. guestList, _ := guestListRepository.Find(eventID) fmt.Printf("guest list: %#v\n", guestList) }
func main() { // Create the event bus that distributes events. eventBus := eventhorizon.NewInternalEventBus() eventBus.AddGlobalHandler(&LoggerSubscriber{}) // Create the event store. eventStore, err := eventhorizon.NewMongoEventStore(eventBus, "localhost", "demo") if err != nil { log.Fatalf("could not create event store: %s", err) } eventStore.RegisterEventType(&InviteCreated{}, func() eventhorizon.Event { return &InviteCreated{} }) eventStore.RegisterEventType(&InviteAccepted{}, func() eventhorizon.Event { return &InviteAccepted{} }) eventStore.RegisterEventType(&InviteDeclined{}, func() eventhorizon.Event { return &InviteDeclined{} }) // Create the aggregate repository. repository, err := eventhorizon.NewCallbackRepository(eventStore) if err != nil { log.Fatalf("could not create repository: %s", err) } // Register an aggregate factory. repository.RegisterAggregate(&InvitationAggregate{}, func(id eventhorizon.UUID) eventhorizon.Aggregate { return &InvitationAggregate{ AggregateBase: eventhorizon.NewAggregateBase(id), } }, ) // Create the aggregate command handler. handler, err := eventhorizon.NewAggregateCommandHandler(repository) if err != nil { log.Fatalf("could not create command handler: %s", err) } // Register the domain aggregates with the dispather. Remember to check for // errors here in a real app! handler.SetAggregate(&InvitationAggregate{}, &CreateInvite{}) handler.SetAggregate(&InvitationAggregate{}, &AcceptInvite{}) handler.SetAggregate(&InvitationAggregate{}, &DeclineInvite{}) // Create the command bus and register the handler for the commands. commandBus := eventhorizon.NewInternalCommandBus() commandBus.SetHandler(handler, &CreateInvite{}) commandBus.SetHandler(handler, &AcceptInvite{}) commandBus.SetHandler(handler, &DeclineInvite{}) // Create and register a read model for individual invitations. invitationRepository, err := eventhorizon.NewMongoReadRepository("localhost", "demo", "invitations") if err != nil { log.Fatalf("could not create invitation repository: %s", err) } invitationRepository.SetModel(func() interface{} { return &Invitation{} }) invitationProjector := NewInvitationProjector(invitationRepository) eventBus.AddHandler(invitationProjector, &InviteCreated{}) eventBus.AddHandler(invitationProjector, &InviteAccepted{}) eventBus.AddHandler(invitationProjector, &InviteDeclined{}) // Create and register a read model for a guest list. eventID := eventhorizon.NewUUID() guestListRepository, err := eventhorizon.NewMongoReadRepository("localhost", "demo", "guest_lists") if err != nil { log.Fatalf("could not create guest list repository: %s", err) } guestListRepository.SetModel(func() interface{} { return &GuestList{} }) guestListProjector := NewGuestListProjector(guestListRepository, eventID) eventBus.AddHandler(guestListProjector, &InviteCreated{}) eventBus.AddHandler(guestListProjector, &InviteAccepted{}) eventBus.AddHandler(guestListProjector, &InviteDeclined{}) // Clear DB collections. eventStore.Clear() invitationRepository.Clear() guestListRepository.Clear() // Issue some invitations and responses. // Note that Athena tries to decline the event, but that is not allowed // by the domain logic in InvitationAggregate. The result is that she is // still accepted. athenaID := eventhorizon.NewUUID() commandBus.HandleCommand(&CreateInvite{InvitationID: athenaID, Name: "Athena", Age: 42}) commandBus.HandleCommand(&AcceptInvite{InvitationID: athenaID}) err = commandBus.HandleCommand(&DeclineInvite{InvitationID: athenaID}) if err != nil { fmt.Printf("error: %s\n", err) } hadesID := eventhorizon.NewUUID() commandBus.HandleCommand(&CreateInvite{InvitationID: hadesID, Name: "Hades"}) commandBus.HandleCommand(&AcceptInvite{InvitationID: hadesID}) zeusID := eventhorizon.NewUUID() commandBus.HandleCommand(&CreateInvite{InvitationID: zeusID, Name: "Zeus"}) commandBus.HandleCommand(&DeclineInvite{InvitationID: zeusID}) // Read all invites. invitations, _ := invitationRepository.FindAll() for _, i := range invitations { fmt.Printf("invitation: %#v\n", i) } // Read the guest list. guestList, _ := guestListRepository.Find(eventID) fmt.Printf("guest list: %#v\n", guestList) // records := eventStore.FindAllEventRecords() // fmt.Printf("event records:\n") // for _, r := range records { // fmt.Printf("%#v\n", r) // } }