DI Container

DI Container intended to make components wiring easier.

All named component instances are exposed on container thru getters and get created upon accessing a getter. Default EventStore and CommandBus components are registered upon container instance creation:

const { Container } = require('node-cqrs');
const container = new Container();

container.eventStore; // instance of EventStore
container.commandBus; // instance of CommandBus

Other components can be registered either as classes or as factories:

// class with automatic dependency injection
container.register(SomeService, 'someService');

// OR factory with more precise control
container.register(cn => new SomeService(cn.commandBus), 'someService');

Container scans class constructors (or constructor functions) for dependencies and injects them, where possible:

class SomeRepository { /* ... */ }

class ServiceA { 
  // named dependency
  constructor(repository) { /* ... */ }
}

class ServiceB { 
  // dependency definition, as a parameter object property
  constructor(options) { 
    this._repository = options.repository;
  }
}

class ServiceC {
  // dependency defined thru parameter object destructuring
  constructor({ repository }) { /* ... */ }
}

// named dependency as a function argument
function ServiceD(repository) {
  this._repository = repository;
}

container.register(SomeRepository, 'repository');
container.register(ServiceA, 'a');
container.register(ServiceB, 'b');
container.register(ServiceC, 'c');
container.register(ServiceD, 'd');

Components that aren't going to be accessed directly by name can also be registered in the container, but their instances will only be created after invoking createUnexposedInstances or createAllInstances method:

container.register(SomeEventObserver);
// at this point the registered observer does not exist

container.createUnexposedInstances();
// now it exists and got all its constructor dependencies

DI container has a set of methods for CQRS components registration:

  • registerAggregate(AggregateType) - registers aggregateCommandHandler, subscribes it to commandBus and wires Aggregate dependencies
  • registerSaga(SagaType) - registers sagaEventHandler, subscribes it to eventStore and wires Saga dependencies
  • registerProjection(ProjectionType, exposedViewName) - registers projection, subscribes it to eventStore and exposes associated projection view on the container
  • registerCommandHandler(typeOrFactory) - registers command handler and subscribes it to commandBus
  • registerEventReceptor(typeOrFactory) - registers event receptor and subscribes it to eventStore

Alltogether:

const { Container, InMemoryEventStorage } = require('node-cqrs');
const container = new Container();

container.registerAggregate(UserAggregate);

// we are using non-persistent in-memory event storage, 
// for a permanent storage you can look at https://www.npmjs.com/package/node-cqrs-mongo
container.register(InMemoryEventStorage, 'storage');

// as an example of UserAggregate dependency
container.register(AuthService, 'authService');

// setup command and event handler listeners
container.createUnexposedInstances();

// send a command
const aggregateId = undefined;
const payload = { profile: {}, password: '...' };
const context = {};
container.commandBus.send('signupUser', aggregateId, { payload, context });

container.eventStore.once('userSignedUp', event => {
  console.log(`user aggregate created with ID ${event.aggregateId}`);
});

In the above example, the command will be passed to an aggregate command handler, which will either restore an aggregate, or create a new one, and will invoke a corresponding method on the aggregate.

After command processing is done, produced events will be committed to the eventStore, and emitted to subscribed projections and/or event receptors.

results matching ""

    No results matching ""