コード例 #1
0
ファイル: provider_test.go プロジェクト: sullirobert/cqrs
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")
	}
}
コード例 #2
0
ファイル: commandbus_test.go プロジェクト: sullirobert/cqrs
// 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)
	}
}
コード例 #3
0
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)
	}
}
コード例 #4
0
ファイル: provider.go プロジェクト: sullirobert/cqrs
// 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
}
コード例 #5
0
ファイル: provider.go プロジェクト: sullirobert/cqrs
// 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
}
コード例 #6
0
ファイル: example_test.go プロジェクト: sullirobert/cqrs
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)))
	}
}
コード例 #7
0
ファイル: cqrs_test.go プロジェクト: sullirobert/cqrs
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
}