func TestPgEventStreamRepoSave(t *testing.T) { typeRegistry := cqrs.NewTypeRegistry() typeRegistry.RegisterEvents( AccountCreatedEvent{}, EmailAddressChangedEvent{}, AccountCreditedEvent{}, AccountDebitedEvent{}, PasswordChangedEvent{}, ) persistance, err := postgres.NewEventStreamRepository( fmt.Sprintf("host=localhost port=5432 user=%s password=%s dbname=cqrs_pg_test sslmode=disable", PG_USER, PG_PASSWORD), typeRegistry, ) if err != nil { t.Fatal(err) } // clear database persistance.GetDb().Exec("TRUNCATE TABLE events") persistance.GetDb().Exec("TRUNCATE TABLE events_integration") persistance.GetDb().Exec("TRUNCATE TABLE events_correlation") repository := cqrs.NewRepository(persistance, typeRegistry) hashedPassword, err := GetHashForPassword("$ThisIsMyPassword1") accountID := "5058e029-d329-4c4b-b111-b042e48b0c5f" account := NewAccount("John", "Snow", "*****@*****.**", hashedPassword, 0.0) account.SetID(accountID) account.ChangePassword("$ThisIsANOTHERPassword") if err := repository.Save(account, "correlationID"); err != nil { t.Fatal(err) } accountFromHistory, err := NewAccountFromHistory(accountID, repository) if err != nil { t.Fatal(err) } if string(accountFromHistory.PasswordHash) != string(account.PasswordHash) { t.Fatal("Expected PasswordHash to match") } if events, err := persistance.AllIntegrationEventsEverPublished(); err != nil { t.Fatal(err) } else if len(events) != 2 { t.Fatal("Expected two events: AccountCreatedEvent, PasswordChangedEvent") } correlationEvents, err := persistance.GetIntegrationEventsByCorrelationID("correlationID") if err != nil { t.Fatal(err) } if len(correlationEvents) == 0 { t.Fatal("Expeced correlation events") } }
// Simple test for publishing and received versioned events using rabbitmq func TestCommandBus(t *testing.T) { // Create a new event bus bus := rabbit.NewCommandBus("amqp://*****:*****@localhost:5672/", "rabbit_testcommands", "testing.commands") // Register types commandType := reflect.TypeOf(SampleCommand{}) commandTypeCache := cqrs.NewTypeRegistry() commandTypeCache.RegisterType(SampleCommand{}) // Create communication channels // // for closing the queue listener, closeChannel := make(chan chan error) // receiving errors from the listener thread (go routine) errorChannel := make(chan error) // and receiving commands from the queue receiveCommandChannel := make(chan cqrs.CommandTransactedAccept) // Start receiving events by passing these channels to the worker thread (go routine) if err := bus.ReceiveCommands(cqrs.CommandReceiverOptions{TypeRegistry: commandTypeCache, Close: closeChannel, Error: errorChannel, ReceiveCommand: receiveCommandChannel, Exclusive: false}); err != nil { t.Fatal(err) } // Publish a simple event to the exchange http://www.rabbitmq.com/tutorials/tutorial-three-python.html log.Println("Publishing Commands") go func() { if err := bus.PublishCommands([]cqrs.Command{cqrs.Command{ CommandType: commandType.String(), Body: SampleCommand{"rabbit_TestCommandBus"}}}); err != nil { t.Fatal(err) } }() // If we dont receive a message within 5 seconds this test is a failure. Use a channel to signal the timeout timeout := make(chan bool, 1) go func() { time.Sleep(5 * time.Second) timeout <- true }() // Wait on multiple channels using the select control flow. select { // Test timeout case <-timeout: t.Fatal("Test timed out") // Version event received channel receives a result with a channel to respond to, signifying successful processing of the message. // This should eventually call an event handler. See cqrs.NewVersionedEventDispatcher() case command := <-receiveCommandChannel: sampleCommand := command.Command.Body.(SampleCommand) log.Println(sampleCommand.Message) command.ProcessedSuccessfully <- true // Receiving on this channel signifys an error has occured work processor side case err := <-errorChannel: t.Fatal(err) } }
func TestInMemoryEventStreamRepository(t *testing.T) { typeRegistry := cqrs.NewTypeRegistry() persistance := cqrs.NewInMemoryEventStreamRepository() repository := cqrs.NewRepository(persistance, typeRegistry) hashedPassword, err := GetHashForPassword("$ThisIsMyPassword1") accountID := "5058e029-d329-4c4b-b111-b042e48b0c5f" if err != nil { t.Fatal("Error: ", err) } log.Println("Get hash for user...") log.Println("Create new account...") account := NewAccount("John", "Snow", "*****@*****.**", hashedPassword, 0.0) account.SetID(accountID) account.ChangePassword("$ThisIsANOTHERPassword") if err := repository.Save(account, "correlationID"); err != nil { t.Fatal(err) } accountFromHistory, err := NewAccountFromHistory(accountID, repository) if err != nil { t.Fatal(err) } if string(accountFromHistory.PasswordHash) != string(account.PasswordHash) { t.Fatal("Expected PasswordHash to match") } if events, err := persistance.AllIntegrationEventsEverPublished(); err != nil { t.Fatal(err) } else { log.Println(events) } correlationEvents, err := persistance.GetIntegrationEventsByCorrelationID("correlationID") if err != nil { t.Fatal(err) } if len(correlationEvents) == 0 { t.Fatal("Expeced correlation events") } log.Println("GetIntegrationEventsByCorrelationID") for _, correlationEvent := range correlationEvents { log.Println(correlationEvent) } }
// Get retrieves events assoicated with an event sourced object by ID func (r *EventStreamRepository) Get(id string) ([]cqrs.VersionedEvent, error) { var version int cbKey := fmt.Sprintf("%s:%s", r.cbPrefix, id) if error := r.bucket.Get(cbKey, &version); error != nil { log.Println("Error getting event source ", id) return nil, error } var events []cqrs.VersionedEvent for versionNumber := 1; versionNumber <= version; versionNumber++ { eventKey := fmt.Sprintf("%s:%s:%d", r.cbPrefix, id, versionNumber) raw := new(cbVersionedEvent) if error := r.bucket.Get(eventKey, raw); error != nil { log.Println("Error getting event :", eventKey) return nil, error } typeRegistry := cqrs.NewTypeRegistry() eventType, ok := typeRegistry.GetTypeByName(raw.EventType) if !ok { log.Println("Cannot find event type", raw.EventType) return nil, errors.New("Cannot find event type " + raw.EventType) } eventValue := reflect.New(eventType) event := eventValue.Interface() if err := json.Unmarshal(raw.Event, event); err != nil { log.Println("Error deserializing event ", raw.Event) return nil, err } versionedEvent := cqrs.VersionedEvent{ ID: raw.ID, SourceID: raw.SourceID, Version: raw.Version, EventType: raw.EventType, Created: raw.Created, Event: reflect.Indirect(eventValue).Interface()} events = append(events, versionedEvent) } return events, nil }
// GetIntegrationEventsByCorrelationID returns all integration events by correlation ID func (r *EventStreamRepository) GetIntegrationEventsByCorrelationID(correlationID string) ([]cqrs.VersionedEvent, error) { var eventsByCorrelationID map[string]cbVersionedEvent correlationKey := "eventstore:correlation:" + correlationID if err := r.bucket.Get(correlationKey, &eventsByCorrelationID); err != nil { return nil, err } typeRegistry := cqrs.NewTypeRegistry() var events []cqrs.VersionedEvent for _, raw := range eventsByCorrelationID { eventType, ok := typeRegistry.GetTypeByName(raw.EventType) if !ok { log.Println("Cannot find event type", raw.EventType) return nil, errors.New("Cannot find event type " + raw.EventType) } eventValue := reflect.New(eventType) event := eventValue.Interface() if err := json.Unmarshal(raw.Event, event); err != nil { log.Println("Error deserializing event ", raw.Event) return nil, err } versionedEvent := cqrs.VersionedEvent{ ID: raw.ID, SourceID: raw.SourceID, Version: raw.Version, EventType: raw.EventType, Created: raw.Created, Event: reflect.Indirect(eventValue).Interface()} events = append(events, versionedEvent) } return events, nil }
func RunScenario(t *testing.T, persistance cqrs.EventStreamRepository) { typeRegistry := cqrs.NewTypeRegistry() bus := rabbit.NewEventBus("amqp://*****:*****@localhost:5672/", "example_test", "testing.example") repository := cqrs.NewRepositoryWithPublisher(persistance, bus, typeRegistry) repository.GetTypeRegistry().RegisterAggregate(&Account{}) repository.GetTypeRegistry().RegisterEvents(AccountCreatedEvent{}, EmailAddressChangedEvent{}, AccountCreditedEvent{}, AccountDebitedEvent{}, PasswordChangedEvent{}) accountID := "5058e029-d329-4c4b-b111-b042e48b0c5f" readModel := NewReadModelAccounts() usersModel := NewUsersModel() eventDispatcher := cqrs.NewVersionedEventDispatchManager(bus, typeRegistry) eventDispatcher.RegisterEventHandler(AccountCreatedEvent{}, func(event cqrs.VersionedEvent) error { readModel.UpdateViewModel([]cqrs.VersionedEvent{event}) usersModel.UpdateViewModel([]cqrs.VersionedEvent{event}) return nil }) eventDispatcher.RegisterEventHandler(AccountCreditedEvent{}, func(event cqrs.VersionedEvent) error { readModel.UpdateViewModel([]cqrs.VersionedEvent{event}) return nil }) eventDispatcher.RegisterEventHandler(AccountDebitedEvent{}, func(event cqrs.VersionedEvent) error { readModel.UpdateViewModel([]cqrs.VersionedEvent{event}) return nil }) eventDispatcher.RegisterEventHandler(EmailAddressChangedEvent{}, func(event cqrs.VersionedEvent) error { readModel.UpdateViewModel([]cqrs.VersionedEvent{event}) usersModel.UpdateViewModel([]cqrs.VersionedEvent{event}) return nil }) eventDispatcher.RegisterEventHandler(PasswordChangedEvent{}, func(event cqrs.VersionedEvent) error { usersModel.UpdateViewModel([]cqrs.VersionedEvent{event}) return nil }) stopChannel := make(chan bool) go eventDispatcher.Listen(stopChannel) testCorrelationID := uuid.New() readModel.LoadAccounts(persistance) usersModel.LoadUsers(persistance) log.Println("Loaded accounts") log.Println(readModel) log.Println("Loaded Users") log.Println(usersModel) log.Println("Create or find an account") readModelAccount := readModel.Accounts[accountID] log.Println(readModelAccount) log.Println("Create or find a user") user := usersModel.Users[accountID] log.Println(user) var account *Account if readModelAccount == nil { log.Println("Get hash for user...") hashedPassword, err := GetHashForPassword("$ThisIsMyPassword1") if err != nil { t.Fatal("Error: ", err) } log.Println("Get hash for user...") log.Println("Create new account...") account = NewAccount("John", "Snow", "*****@*****.**", hashedPassword, 0.0) log.Println("Set ID...") account.SetID(accountID) log.Println(account) } else { account, _ = NewAccountFromHistory(accountID, repository) } log.Println(account) log.Println(readModel) log.Println(usersModel) account.ChangePassword("$ThisIsANOTHERPassword") if !account.CheckPassword("$ThisIsANOTHERPassword") { t.Fatal("Password is incorrect for account") } log.Println("Change email address and credit the account") account.ChangeEmailAddress("*****@*****.**") account.Credit(50) account.Credit(50) log.Println(account) log.Println(readModel) log.Println("Persist the account") repository.Save(account, "") log.Println(readModel) log.Println(usersModel) log.Println("Load the account from history") account, error := NewAccountFromHistory(accountID, repository) if error != nil { t.Fatal(error) } log.Println(account) log.Println(readModel) log.Println("Change the email address, credit 150, debit 200") lastEmailAddress := "*****@*****.**" account.ChangeEmailAddress(lastEmailAddress) account.Credit(150) account.Debit(200) log.Println(account) log.Println(readModel) log.Println("Persist the account") repository.Save(account, testCorrelationID) log.Println(readModel) log.Println(usersModel) log.Println("Load the account from history") account, error = NewAccountFromHistory(accountID, repository) if error != nil { t.Fatal(error) } time.Sleep(100 * time.Millisecond) // All events should have been replayed and the email address should be the latest log.Println(account) log.Println(readModel) // log.Println(usersModel) if account.EmailAddress != lastEmailAddress { t.Fatal("Expected emailaddress to be ", lastEmailAddress) } log.Println("Stop channel") stopChannel <- true log.Println("GetIntegrationEventsByCorrelationID") correlationEvents, err := persistance.GetIntegrationEventsByCorrelationID(testCorrelationID) if err != nil { t.Fatal(err) } if len(correlationEvents) == 0 { t.Fatal("Expected correlation events") } for _, correlationEvent := range correlationEvents { log.Println(glr.Green(fmt.Sprintf("%v", correlationEvent.Event))) } }
func TestScenario(t *testing.T) { log.SetFlags(log.LstdFlags | log.Lshortfile) // Type Registry typeRegistry := cqrs.NewTypeRegistry() // Event sourcing persistance := cqrs.NewInMemoryEventStreamRepository() bus := cqrs.NewInMemoryEventBus() repository := cqrs.NewRepositoryWithPublisher(persistance, bus, typeRegistry) typeRegistry.RegisterAggregate(&Account{}, AccountCreatedEvent{}, EmailAddressChangedEvent{}, AccountCreditedEvent{}, AccountDebitedEvent{}, PasswordChangedEvent{}) // Read Models readModel := NewReadModelAccounts() usersModel := NewUsersModel() // Command Handlers commandBus := cqrs.NewInMemoryCommandBus() commandDispatcher := cqrs.NewCommandDispatchManager(commandBus, typeRegistry) RegisterCommandHandlers(commandDispatcher, repository) // Integration events eventDispatcher := cqrs.NewVersionedEventDispatchManager(bus, typeRegistry) integrationEventsLog := cqrs.NewInMemoryEventStreamRepository() RegisterIntegrationEventHandlers(eventDispatcher, integrationEventsLog, readModel, usersModel) commandDispatcherStopChannel := make(chan bool) eventDispatcherStopChannel := make(chan bool) go commandDispatcher.Listen(commandDispatcherStopChannel, false) go eventDispatcher.Listen(eventDispatcherStopChannel, false) log.Println("Dump models") log.Println(readModel) log.Println(usersModel) log.Println("Find an account") readModelAccount := readModel.Accounts[accountID] log.Println(readModelAccount) log.Println("Find a user") user := usersModel.Users[accountID] log.Println(user) hashedPassword, err := GetHashForPassword("$ThisIsMyPassword1") if err != nil { t.Fatal("Error: ", err) } log.Println("Create new account...") createAccountCommand := cqrs.CreateCommand( CreateAccountCommand{"John", "Snow", "*****@*****.**", hashedPassword, 0.0}) commandBus.PublishCommands([]cqrs.Command{createAccountCommand}) log.Println("Dump models") log.Println(readModel) log.Println(usersModel) log.Println("Change Password") changePasswordCommand := cqrs.CreateCommand( ChangePasswordCommand{accountID, "$ThisIsANOTHERPassword"}) commandBus.PublishCommands([]cqrs.Command{changePasswordCommand}) log.Println("Change email address and credit the account") changeEmailAddressCommand := cqrs.CreateCommand( ChangeEmailAddressCommand{accountID, "*****@*****.**"}) creditAccountCommand := cqrs.CreateCommand( CreditAccountCommand{accountID, 50}) creditAccountCommand2 := cqrs.CreateCommand( CreditAccountCommand{accountID, 50}) commandBus.PublishCommands([]cqrs.Command{ changeEmailAddressCommand, creditAccountCommand, creditAccountCommand2}) log.Println("Dump models") log.Println(readModel) log.Println(usersModel) log.Println("Change the email address, credit 150, debit 200") lastEmailAddress := "*****@*****.**" changeEmailAddressCommand = cqrs.CreateCommand( ChangeEmailAddressCommand{accountID, lastEmailAddress}) creditAccountCommand = cqrs.CreateCommand( CreditAccountCommand{accountID, 150}) debitAccountCommand := cqrs.CreateCommand( DebitAccountCommand{accountID, 200}) commandBus.PublishCommands([]cqrs.Command{ changeEmailAddressCommand, creditAccountCommand, debitAccountCommand}) log.Println("Dump models") log.Println(readModel) log.Println(usersModel) time.Sleep(300 * time.Millisecond) log.Println("Dump history - integration events") if history, err := repository.GetEventStreamRepository().AllIntegrationEventsEverPublished(); err != nil { t.Fatal(err) } else { for _, event := range history { log.Println(event) } } log.Println("GetIntegrationEventsByCorrelationID") correlationEvents, err := repository.GetEventStreamRepository().GetIntegrationEventsByCorrelationID(debitAccountCommand.CorrelationID) if err != nil || len(correlationEvents) == 0 { t.Fatal(err) } for correlationEvent := range correlationEvents { log.Println(correlationEvent) } log.Println("Load the account from history") account, error := NewAccountFromHistory(accountID, repository) if error != nil { t.Fatal(error) } // All events should have been replayed and the email address should be the latest log.Println("Dump models") log.Println(account) log.Println(readModel) log.Println(usersModel) if account.EmailAddress != lastEmailAddress { t.Fatal("Expected emailaddress to be ", lastEmailAddress) } if account.Balance != readModel.Accounts[accountID].Balance { t.Fatal("Expected readmodel to be synced with write model") } eventDispatcherStopChannel <- true commandDispatcherStopChannel <- true }