juicycleff / nestjs-event-store

NestJS CQRS module for EventStore.org. It requires @nestjs/cqrs
https://xraph.com
MIT License
199 stars 29 forks source link

Event not handled by event store. #9

Closed imi187 closed 4 years ago

imi187 commented 4 years ago

As soon as I implement the "nestjs-event-store" like in your branche "next" in 'ultimate-backend' the handler is not called + the event is not received in the event store

app.module.ts

@Module({
  imports: [
    ProductModule,
    EventStoreModule.register({
      tcpEndpoint: {
        host: 'http://localhost',
        port: 1113,
      },
      options: {
        defaultUserCredentials: {
          username: 'admin'
          password: 'changeit',
',
        },
      },
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule { }

product.module.ts

const productEventHandlers = [
  ProductCreatedHandler
];

@Module({
  imports: [
    CqrsModule,
    EventStoreModule.registerFeature({
      subscriptions: [
        {
          type: EventStoreSubscriptionType.CatchUp,
          stream: '$ce-product',
          resolveLinkTos: true, // Default is true (Optional)
          lastCheckpoint: 13, // Default is 0 (Optional)
        },
        {
          type: EventStoreSubscriptionType.Volatile,
          stream: '$ce-product',
        },
        {
          type: EventStoreSubscriptionType.Persistent,
          stream: '$ce-product',
          persistentSubscriptionName: 'steamName',
        },
      ],
      eventHandlers: {
        ProductCreatedEvent: (id, data) => new ProductCreatedEvent(id, data),
      },
    }),
  ],
  providers: [
    ProductService,
    ...commandHandler,
    ...queryHandler,
    ...productEventHandlers,
  ],
  controllers: [],
  exports: [ProductService]
})
export class ProductModule { }

product.handler.command.create.ts

import { CommandHandler, ICommandHandler, EventBus } from "@nestjs/cqrs";

@CommandHandler(CreateProductCommand)
export class CreateProductCommandHandler implements ICommandHandler<CreateProductCommand> {

  constructor(private readonly eventBus: EventBus) { }

  async execute(command: CreateProductCommand) {
    const { dto } = command;
    this.eventBus.publish(new ProductCreatedEvent("1", dto));
    return {};
  }

}

product-created-event.ts

import { IEvent } from '@nestjs/cqrs';

export class ProductCreatedEvent implements IEvent {
    constructor(
        public _id: string,
        public readonly data: any
    )  { }
}

product-created.handler.ts

import { IEventHandler, EventsHandler } from '@nestjs/cqrs';

@EventsHandler(ProductCreatedEvent)
export class ProductCreatedHandler
  implements IEventHandler<ProductCreatedEvent> {
  handle(event: ProductCreatedEvent) {
    Logger.log(event, 'ProductCreatedEvent'); // write here
  }
}

Do I miss something or is this a bug?

Nice job btw!

juicycleff commented 4 years ago

@imi187 You need to go to the eventstore.org dashboard and enable and enable the propagation of events

imi187 commented 4 years ago

Hey @juicycleff , what do you mean by 'enable propagation of events'?

Log terminal:

Connecting to persistent subscription steamName on stream $ce-product!
[Nest] 739748   - 04/23/2020, 10:34:48 AM   [EventStore] Volatile and subscribing to stream $ce-product!
[Nest] 739748   - 04/23/2020, 10:34:48 AM   [InstanceLoader] EventStoreCoreModule dependencies initialized +0ms
[Nest] 758036   - 04/23/2020, 11:58:52 AM   [NestjsEventStore] EventStore closed! +1141336ms
[Nest] 758036   - 04/23/2020, 11:58:52 AM   [EventStore] Object:
{}
 +3ms
[Nest] 758036   - 04/23/2020, 11:58:52 AM   [EventStore] Connection 'ES-df570043-b45e-4e84-ad84-10dcc3e8bc58' was closed. +2ms
[Nest] 758036   - 04/23/2020, 11:58:52 AM   [EventStore] onDropped => Error: Connection 'ES-df570043-b45e-4e84-ad84-10dcc3e8bc58' was closed. Waiting Operation ReadStreamEventsForwardOperation (e049e697-a4ca-4eb2-a7c2-499c6024dce0): Stream: $ce-product, FromEventNumber: 14, MaxCount: 500, ResolveLinkTos: true, RequireMaster: true, retry count: 0, created: 09:39:51.020, last updated: 09:39:51.020. +2ms
[Nest] 758036   - 04/23/2020, 12:17:40 PM   [NestjsEventStore] EventStore closed! +1128320ms

In dashboard no connections.

In daypaio/nestjs-eventstore it connects. Same problem my event is not received in event-store (Handler works only internal)

dashboard

persistent_sub

terminal

After a while:

error

juicycleff commented 4 years ago

@imi187 if I understand correctly, your published events do not get published to the eventstore right?

juicycleff commented 4 years ago

@imi187 here is the problem, I think I need to make this clear, or specify options to solve this, but here is the solution.

The rule of thumb is your event constructor arg name must match your stream name. Like this

export class ProductCreatedEvent implements IEvent {
    constructor(
        public readonly product: any
    )  { }
}
import { CommandHandler, ICommandHandler, EventBus } from "@nestjs/cqrs";

@CommandHandler(CreateProductCommand)
export class CreateProductCommandHandler implements ICommandHandler<CreateProductCommand> {

  constructor(private readonly eventBus: EventBus) { }

  async execute(command: CreateProductCommand) {
    const { dto } = command;
    this.eventBus.publish(new ProductCreatedEvent(dto));
    return {};
  }
}

So if your subscription handler name is $ce-product$, then your event implementation constructor name much be product without $ce-

juicycleff commented 4 years ago

I will make an update to allow other options. The design was more microservice oriented and you can only ever publish to one domain name in any one microservices. But I see where this should be more robust

imi187 commented 4 years ago

@juicycleff, Thanks for the quick support. It works like a charm ;)

hbthegreat commented 4 years ago

Hello fellow devs!

I came across this issue while running into it myself. After following these steps I am still seeing the same results. I feel like I must be missing something small and didn't think it was worth creating another issue for it as it is probably related.

Firing up it seems to be able to connect and subscribe. image

AppModule.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AssetController } from './asset/controllers/assets.controller';
import { AssetModule } from './asset/asset.module';
import { AppService } from './app.service';
import { EventStoreModule } from '@juicycleff/nestjs-event-store';
import { RethinkModule } from './document-store/rethink.module';
@Module({
  imports: [
    RethinkModule,
    EventStoreModule.register({
      tcpEndpoint: {
        host: 'http://localhost', // process.env.ES_TCP_HOSTNAME || AppConfig.eventstore?.hostname,
        port: 1113,//parseInt(process.env.ES_TCP_PORT, 10) || AppConfig.eventstore?.tcpPort,
      },
      options: {
        maxRetries: 1000, // Optional
        maxReconnections: 1000,  // Optional
        reconnectionDelay: 1000,  // Optional
        heartbeatInterval: 1000,  // Optional
        heartbeatTimeout: 1000,  // Optional
        defaultUserCredentials: {
          password: 'admin', //AppConfig.eventstore?.tcpPassword,
          username: 'changeit' //AppConfig.eventstore?.tcpUsername,
        },
      },
    }),
    AssetModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

asset.module.ts

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { EventStoreSubscriptionType, EventStoreModule } from '@juicycleff/nestjs-event-store';
import { QueryHandlers } from './queries/handlers';
import { EventHandlers } from './events/handlers';
import { CommandHandlers } from './commands/handlers';
import { AssetCreatedEvent } from './events/impl/asset-created.event';
import { AssetSagas } from './sagas/asset.sagas';
import { AssetUpdatedEvent } from './events/impl/asset-updated.event';
import { AssetDeletedEvent } from './events/impl/asset-deleted.event';
import { AssetController } from './controllers/assets.controller';
import { AssetService } from './services/asset.service';
import { AssetRepository } from './repository/asset.repository';
import { RethinkModule } from '../document-store/rethink.module';

@Module({
    imports: [
        RethinkModule,
        CqrsModule,
        EventStoreModule.registerFeature({
            featureStreamName :'$ce-asset',
            subscriptions: [
                {
                    type: EventStoreSubscriptionType.CatchUp,
                    stream: '$ce-asset'
                }
            ],
            eventHandlers: {
                AssetCreatedEvent: (data) => new AssetCreatedEvent(data),
                AssetUpdatedEvent: (data) => new AssetUpdatedEvent(data),
                AssetDeletedEvent: (data) => new AssetDeletedEvent(data)
            }
        })
    ],
    controllers: [AssetController],
    providers: [
        AssetService,
        AssetSagas,
        AssetRepository,
        ...QueryHandlers,
        ...CommandHandlers,
        ...EventHandlers,
    ],
    exports: [
        AssetService
    ]
})

export class AssetModule {}

Hitting this handler

import { ICommandHandler, CommandHandler, EventBus } from '@nestjs/cqrs';
import { CreateAssetCommand } from '../impl/create-asset.command';
import { Logger } from '@nestjs/common';
import { AssetCreatedEvent } from '../../events/impl/asset-created.event';

@CommandHandler(CreateAssetCommand)
export class CreateAssetHandler implements ICommandHandler<CreateAssetCommand> {
  constructor(
    private readonly eventBus: EventBus
  ) {}

  async execute(command: CreateAssetCommand) {
    Logger.log('Async CreateAssetHandler...', 'CreateAssetCommand');

    const { assetDto } = command;
    console.log(assetDto);
    this.eventBus.publish(new AssetCreatedEvent(assetDto));
    return {};
  }
}

image

asset-created.event.ts

import { IEvent } from '@nestjs/cqrs';

export class AssetCreatedEvent implements IEvent {
  constructor(
    public readonly asset: any
  ) {}
}

Then after some time I also run into EventStore closed! image

I don't see any of the events getting published into eventstore either.

Any ideas?

Thanks for all your hard work I have learnt a lot from your ultimate backend and this project :)

imi187 commented 4 years ago
defaultUserCredentials: {
    password: 'admin', 
    username: 'changeit'
},

Better swap this ;)

hbthegreat commented 4 years ago

Haha yes they are only for local dev. I have injected environments for prod haha.

hbthegreat commented 4 years ago

@juicycleff would it be better to make my request a different post? Happy to if you would prefer. Still haven't found a resolution.

claridgicus commented 4 years ago

@juicycleff I'm having the same issues as @hbthegreat