How to use single Kafka Client to all services? [duplicate] - node.js

I have a problem with using multiple ClientKafka in one service, here is my implementation:
#Controller()
export class ApiController implements OnModuleInit {
constructor(
#Inject("ACCOUNT_SERVICE") private readonly accountKafkaClient: ClientKafka,
#Inject("WORKSPACE_SERVICE") private readonly workspaceKafkaClient: ClientKafka
) { }
async onModuleInit() {
const requestPatterns = [
'topic'
];
requestPatterns.forEach((pattern) => {
this.accountKafkaClient.subscribeToResponseOf(`account.${pattern}`);
});
await this.accountKafkaClient.connect();
}
async onModuleDestroy() {
await this.accountKafkaClient.close();
}
#Get()
async sendMessage() {
const data = {
msg: "account.topic"
}
const kafkaResponse = this.accountKafkaClient.send<any>('account.topic', JSON.stringify(data));
const response = await firstValueFrom(kafkaResponse);
const kafkaResponse2 = this.workspaceKafkaClient.send<any>('workspace.topic', JSON.stringify(response )) //THIS IS NOT RUNNING, WORKSPACE_SERVICE NOT RECEIVE ANY MESSAGE
return await firstValueFrom(kafkaResponse2);
}
}
can someone tell me why workspaceKafkaClient is not sending any message to WORKSPACE_SERVICE microservice? I try with passing this client in onModule... functions like accountKafkaClient but it didn't help me,
here is also my settings in module:
#Module({
imports: [
ClientsModule.register([
{
name: 'ACCOUNT_SERVICE',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'account_service',
brokers: ['localhost:29092'],
},
consumer: {
groupId: 'account-consumer',
},
},
},
{
name: 'WORKSPACE_SERVICE',
transport: Transport.KAFKA,
options: {
client: {
clientId: 'workspace_service',
brokers: ['localhost:29092'],
},
consumer: {
groupId: 'workspace-consumer',
},
},
},
]),
],
controllers: [ApiController],
providers: [
ApiService,
// KafkaProducerProvider,
],
})
export class ApiModule {}
thanks for any help!

You only need one producer client per application, but Kafka producers never immediately send data to brokers.
You need to flush them for that to happen, which is what await firstValueFrom(...) should do, but you've not shown that method.
Otherwise, you seem to be trying to get the reponse from one topic to send to another, which is what a consumer should be used for, rather than blocking on one producer request.

Related

NestJS - #golevelup/nestjs-rabbitmq problem

I want my service to constantly print all incoming messages from a RabbitMQ queue to the console.
For this, I am using the #golevelup/nestjs-rabbitmq package and I have the RabbitMQModule configured the following way:
{
uri: 'amqp://localhost:5672',
exchanges: [
{
name: 'the_cats_exchange',
type: 'direct',
options: {
durable: true
}
}
],
channels: {
'channel-1': {
default: true
}
}
}
I also have a service that should print the messages to the console:
import { RabbitSubscribe } from "#golevelup/nestjs-rabbitmq";
import { Injectable } from "#nestjs/common";
#Injectable()
export class RabbitMQMessagingService {
#RabbitSubscribe({
exchange: 'the_cats_exchange',
routingKey: 'test_routing_key',
queue: 'cats',
queueOptions: { durable: true }
})
async handleMessage(msg: {}) {
console.log(msg);
}
}
For some reason, even though I am publishing messages to "the_cats_exchange" with the routing key "test_routing_key" no messages are being printed to the console.
I made sure the queue "cats" is bound to "the_cats_exchange" with the "test_routing_key" routing key, so this can't be the problem.

#nestjs/typeorm How to get current connection with v0.3.0 of typeorm in a custom decorator?

I used to have:
import { getManager } from "typeorm";
return getManager()
.count(entity, {
...where,
})
.then((count) => count < 1);
to use the current connection in a validation decorator and access to database.
But now with the version 0.3.0 of typeorm, getManager() is deprecated and I get the following error:
`ConnectionNotFoundError: Connection default was not found`
How should I use the new DataSource API to get the current connection using Nest.js in external scripts like validation decorators?
I have dealt with the following error with "typeorm": "0.3.6", before, so hopes that my experience will be relevant for you.
Instead of using getManager I am using entityManager but result is the same.
Here is the example for your case:
test.module.ts
#Module({
imports: [
TypeOrmModule.forRoot(postgresConfig), // <- connection configs
TypeOrmModule.forFeature([
TestEntity,
]),
],
controllers: [],
providers: [TestService],
})
export class TestModule {}
test.service.ts
#Injectable()
export class TestService implements OnApplicationBootstrap {
constructor(
#InjectEntityManager()
private readonly entityManager: EntityManager,
)
async onApplicationBootstrap(): Promise<void> {
const count = await this.entityManager.getRepository(TestEntity).count('query');
console.log(count);
}
}
You should also have created a typeorm entities for that.
If we are talking about migrations you could use an ormconfig.json, but also there was a problem with using typeorm combined with test frameworks like Jest and if you case is relevant with it, just mention it, and I'll find an example in on of my projects.
It should be something relevant with creating manual connections without decorator like:
export const getDatabaseConnectionOptions = (config): PostgresConnectionOptions => {
return {
type: 'postgres',
logging: config.db.logging,
connectTimeoutMS: config.db.master.connectionTimeoutMillis,
uuidExtension: 'uuid-ossp',
extra: {
idleTimeoutMillis: config.db.master.idleTimeoutMillis,
max: config.db.master.max,
keepalives_idle: config.db.master.keepalives_idle,
},
entities: process.env.TS_NODE
? [path.join(process.cwd(), 'src/**/*.entity.ts')]
: [path.join(process.cwd(), 'dist/**/*.entity.js')],
migrations: process.env.TS_NODE ? ['src/typeorm_migrations/*.ts'] : ['dist/typeorm_migrations/*.js'],
migrationsTableName: 'typeorm_migrations',
namingStrategy: new DatabaseNamingStrategy(),
replication: {
master: {
host: config.db.master.host,
port: config.db.master.port,
username: config.db.master.user,
password: config.db.master.password,
database: config.db.master.database,
},
slaves: [
{
host: config.db.slave.host,
port: config.db.slave.port,
username: config.db.slave.user,
password: config.db.slave.password,
database: config.db.slave.database,
},
],
},
cli: {
migrationsDir: 'src/typeorm_migrations',
},
};
};
export const createDatabaseConnection = async (config): Promise<Connection> => {
// Setup TypeDI
useContainer(Container);
// Setup typeorm-transactional-cls-hooked
initializeTransactionalContext();
patchTypeORMRepositoryWithBaseRepository();
return await createConnection(config);
};
const connection = await createDatabaseConnection(getDatabaseConnectionOptions(config));
const { app } = createAppApi();
and then access to managers or entities via connection props.

Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId

I want to do inheritance in Hyperledger Fabric Chaincode using NodeJs.
I have created two classes CommonContract and AdminContract. CommonContract is the base class and AdminContract child class. But I got error when I invoke getLastPatiendId function from AdminContract. Error is as follows.
Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId
[nodemon] app crashed - waiting for file changes before starting...
Even though getLastPatientId function is written in contract it is giving error function does not exists.
Below are the code of AdminContract, CommonContract, index.js file for chaincode and the server API which invoke transaction
CommonContract.js
This is CommonContract smart contract which is a base class.
'use strict';
const { Contract } = require('fabric-contract-api');
class CommonContract extends Contract {
async initLedger(ctx) {
console.info('============= START : Initialize Ledger ===========');
const initData = [
{
"firstName": "ABC",
"middleName": "D",
"lastName": "BCA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
},
{
"firstName": "Five",
"middleName": ".H.",
"lastName": "CDA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
}
]
for (let i = 0; i < initData.length; i++) {
initData[i].docType = 'patient';
await ctx.stub.putState('PID' + i, Buffer.from(JSON.stringify(initData[i])));
console.log('Data Added:---', initData[i]);
}
}
async getPatient(ctx, patientId){
const patient = await ctx.stub.getState(patientId);
if(patient.length || patient.length > 0)
console.log(patient);
let data = JSON.parse(patient.toString());
return data;
}
async getAllPatient(ctx){
const startKey = '';
const endKey = '';
const allResults = [];
for await (const {key, value} of ctx.stub.getStateByRange(startKey, endKey)){
const strValue = Buffer.from(value).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
} catch (err) {
console.log(err);
record = strValue;
}
allResults.push({ Key: key, Record: record });
}
console.info(allResults);
return JSON.stringify(allResults);
}
}
module.exports = CommonContract;
AdminContract.js
This is an AdminContract smart contract that inherits CommonContract and has a single function getLastPatientId() it generally returns the patient id of the last patient created in the ledger.
'use strict';
let Patient = require('./PatientAssets.js');
const CommonContract = require('./CommonContract.js');
class AdminContract extends CommonContract {
async getLastPatientId(ctx) {
let result = await getAllPatient(ctx);
console.log(result);
return result[result.length - 1].patiendId;
}
}
module.exports = AdminContract;
index.js
This file is main entry point all smart contract.
'use strict';
const CommonContract = require('./lib/CommonContract.js');
const AdminContract = require('./lib/AdminContract.js');
module.exports.contracts = [CommonContract, AdminContract];
admin.js
This is server side route file which invoke the getLastPatientId function which is in AdminContract.js smart contract file.
router.get('/getAllPatient', async (req, res) => {
try {
// load the network configuration
const ccpPath = path.resolve(__dirname, '..', '..', 'network-config', 'organizations', 'peerOrganizations', 'doctor.hospital_network.com', 'connection-doctor.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('appUser1');
if (!identity) {
console.log('An identity for the user "appUser" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'appUser1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('hospital');
// Get the contract from the network.
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('getLastPatientId');
const data = JSON.parse(result);
console.log(`Transaction has been evaluated, result is: ${result}`);
// Disconnect from the gateway.
await gateway.disconnect();
return data;
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
});
I found out that evalutateTransaction() invokes the function in CommonContract but was not able to invoke AdminContract functions. Please help me out. What is the error in my code ?
This is because you need to specify the contract name when calling transactions, except for the first contract which is treated as a default. For example, you should be able to successfully call initLedger, CommonContract:initLedger, and AdminContract:getLastPatientId but getLastPatientId will fail because there is no such transaction on the default contract.
You can see what transactions are available, and which contract is the default, by getting the metadata for the contract. You can get the metadata using the org.hyperledger.fabric:GetMetadata transaction, where org.hyperledger.fabric is the system contract and GetMetadata is the transaction. The ccmetadata utility will call that get metadata transaction if that helps.
You can also customise the contract names using a constructor. For example, to call an Admin:getLastPatientId transaction, add the following constructor:
class AdminContract extends Contract {
constructor() {
super('Admin');
}
//...
}
Note: I don't think it's related to your current problem but I'm not sure why you want to use inheritance in this case. It might cause other problems so I would stick to extending Contract as shown above.
First I was implementing multiple contracts.
By getting the metadata for the contracts there is one default contract.
{
CommonContract: {
name: 'CommonContract',
contractInstance: { name: 'CommonContract', default: true },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
AdminContract: {
name: 'AdminContract',
contractInstance: { name: 'AdminContract' },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
DoctorContract: {
name: 'DoctorContract',
contractInstance: { name: 'DoctorContract' },
transactions: [ [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
PatientContract: {
name: 'PatientContract',
contractInstance: { name: 'PatientContract' },
transactions: [ [Object], [Object] ],
info: { title: '', version: '' }
},
'org.hyperledger.fabric': {
name: 'org.hyperledger.fabric',
contractInstance: { name: 'org.hyperledger.fabric' },
transactions: [ [Object] ],
info: { title: '', version: '' }
}
}
Now in the admin.js file, looking at the line where we getting the contract
const contract = network.getContract('basic');
Here this statement was using default contract i.e. CommonContract, But I was implementing multiple contracts so I have to give the reference of that contract, so I make this change.
const contract = network.getContract('basic', 'AdminContract');
and now when I invoke the contract
const result = await contract.evaluateTransaction('getLastPatientId');
or
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('AdminContract:getLastPatientId');
It works...

NestJS - how to call Service method in Module options

I'm trying out a event sourced NestJS application.
I'm stuck at the following point:
In my GamesModule I'm setting up the Eventstore connection to my stream.
In these options there are write and read functions which are called by the library to update/ read the stream's last checkpoint position.
I'd like to call my service methods that write and read to/from the database from these functions.
I've tried injecting the service in the constructor, but because the register function is and has to be a static method, I don't have access to whatever is injected in the constructor.
Is it possible to use a service or repository in the Dynamic module's options?
Service writing to and reading from DB:
#Injectable()
export class EventStoreStateService {
private readonly logger = new Logger(EventStoreStateService.name);
constructor(
#InjectRepository(EventStoreState)
private eventStoreStateRepository: Repository<EventStoreState>,
) {}
updateCheckpoint(stream: string, position: number) {
const updated = this.eventStoreStateRepository.update(
{ lastCheckpoint: position },
{ streamName: stream },
);
this.logger.log({ updated });
return updated;
}
getLastCheckpoint(stream: string) {
const last = this.eventStoreStateRepository.findOne({
where: { streamName: stream },
});
this.logger.log({ last });
return last;
}
}
The module where I setup the event-store connection. In the useFactory store.write(key: string, value: number) I'd like to call my service methods
#Module({
imports: [EventStoreStateModule],
})
export class GamesModule {
constructor(
// no access to this service in the static method
//
#Inject(EventStoreStateService)
private readonly eventStoreStateService: EventStoreStateService,
) {}
static register(): // updateCheckpoint: (key: string, value: number) => Promise<number>,
// getLastCheckpoint: (key: string) => Promise<number>,
DynamicModule {
return {
module: GamesModule,
imports: [
CqrsModule,
EventStoreModule.registerFeatureAsync({
type: 'event-store',
useFactory: async (...args) => {
console.log({ args });
return {
featureStreamName: '$ce-game',
type: 'event-store',
subscriptions: [
{
type: EventStoreSubscriptionType.CatchUp, // research various types
stream: '$ce-game',
resolveLinkTos: true,
},
],
eventHandlers: EventStoreInstanciators,
store: {
storeKey: 'game',
write: async (key: string, value: number) => {
// TODO: on every new event for stream x this function
// is called with the last position number
// problem: we need access to the service that connects
// to ORM, but it's a static method so no access to whatever
// is injected in the constructor
//
},
read: async (key: string) => {
// same as write function
//
},
clear: () => null,
},
};
},
}),
TypeOrmModule.forFeature([GameProjection]),
],
controllers: [GamesController],
providers: [
GamesResolver,
GamesService,
GamesRepository,
...CommandHandlers,
...EventHandlers,
],
};
}
}
Using this library for event-store connection: https://github.com/juicycleff/nestjs-event-store.
Key things to know in NestJs.
If you want to access service within the same module, make sure you inject service in your constructor like constructor( private readonly eventStoreStateService: EventStoreStateService){}
If you want to access service from another module then you have to export the service in .module.ts exports: [EventStoreStateService] and then also inject in service or controller where you want to use it.

NestJS - Cannot inject a service into a subscriber

I have a subscriber for NestJS to listen to any create, update or delete events (TypeORM). When one of these events is fired, I'd like to use an injected service in order to create a new revision entry.
However, it seems I cannot get the dependency loaded inside of the subscriber and the service comes back as being undefined
Key files:
EntityModificationSubscriber (Subscriber)
RevisionEntity (Entity)
app.module.ts
#Module({
imports: [
HttpModule,
TypeOrmModule.forRoot({
type: (process.env.DB_TYPE as any) || 'postgres',
host: process.env.DB_HOST || '127.0.0.1',
port: (process.env.DB_PORT as any) || 5432,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: process.env.DB_NAME || 'test',
entities: [join(__dirname, '**/**.entity{.ts,.js}')],
synchronize: true,
logging: 'all',
logger: 'advanced-console',
subscribers: [EntityModificationSubscriber],
}),
TypeOrmModule.forFeature([
RevisionEntity,
]),
TerminusModule.forRootAsync({
// Inject the TypeOrmHealthIndicator provided by nestjs/terminus
inject: [TypeOrmHealthIndicator, MicroserviceHealthIndicator],
useFactory: (db, msg) => getTerminusOptions(db, msg),
}),
GraphQLModule.forRoot({
debug: true,
playground: true,
typePaths: ['./**/*.graphql'],
}),
],
controllers: [AppController],
providers: [
RevisionService,
EntityModificationSubscriber,
],
})
entity_modification_subscriber.ts
import {EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent} from 'typeorm';
import {RevisionEntity, RevisonEntityStatus} from '../entities/revison.entity';
import {RevisionService} from '../services/revisions.service';
import {Injectable} from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private revisionService: RevisionService) {
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
The only way I found to inject a dependency into a subscriber using NestJS, was not to register that subscriber in the TypeORM configuration. I subscribe it manually into the TypeORM connection on subscriber's constructor.
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent, Connection } from 'typeorm';
import { RevisionEntity, RevisonEntityStatus } from '../entities/revison.entity';
import { RevisionService } from '../services/revisions.service';
import { Injectable } from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private readonly connection: Connection, private readonly revisionService: RevisionService) {
connection.subscribers.push(this); // <---- THIS
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
//this.revisionService <- should be working!
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
Then in your app.module on the TypeORM module configuration (TypeOrmModule.forRoot).
Remove the line:
subscribers: [EntityModificationSubscriber],
It solved to me, hope it help others.
You can find discussions about that in some NestJS issues/pull requests.
https://github.com/nestjs/typeorm/issues/85
https://github.com/nestjs/typeorm/pull/27
Hi the problem is that If you want to preform database actions you would have to use:
event.manager
and Don't use getEntityManager() or getRepository() or any other global function.
This is due to a transaction. Save is running in a transaction and your data is saved in a transaction which is not committed yet. But global functions you use are running out of transaction.
more about issue here:
https://github.com/typeorm/typeorm/issues/681

Resources