-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: Multi import support (Backward Compatible) #47
base: master
Are you sure you want to change the base?
Conversation
Also, it is not good that we already have a service name, which makes the new |
@falahati, wow! Great PR. I will look into it later today. I think we can break backward compatibility and include this in version 3 with better code quality as you mentioned. |
Yes!
That's right. I think we need to make
export interface IRMQServiceBaseOptions {
defaultConfig?: IRMQConnectionOptions;
serviceName?: string;
logMessages?: boolean;
logger?: LoggerService;
} export interface IRMQConnectionOptions extends IRMQInstanceOptions, IRMQTransortOptions {
} export interface IRMQInstanceOptions {
connections: IRMQConnection[];
reconnectTimeInSeconds?: number;
heartbeatIntervalInSeconds?: number;
} export interface IRMQTransortOptions {
exchangeName: string;
queueName?: string;
queueArguments?: {
[key: string]: string;
};
prefetchCount?: number;
isGlobalPrefetchCount?: boolean;
isQueueDurable?: boolean;
isQueueExclusive?: boolean;
isExchangeDurable?: boolean;
assertExchangeType?: Parameters<Channel['assertExchange']>[1];
exchangeOptions?: Options.AssertExchange;
messagesTimeout?: number;
middleware?: typeof RMQPipeClass[];
intercepters?: typeof RMQIntercepterClass[];
errorHandler?: typeof RMQErrorHandler;
} This will allow us to get default connection if no other needed. Backward compatability can be supported if we can work with old and new config at the same time: Then in child module one can define another connection with different options: export interface IRMQFeatureConnectionOptions {
config: IRMQConnectionOptions | IRMQTransortOptions;
name: string | Symbol;
} If full config is provided - we make another connection to another instance. If not - using default connection, but may assert different exchange or listen to different queue. Then, you can use provided @RMQInjectService('my-name') rmqService: RMQService And in export interface IRouteOptions {
name?: string | string[];
manualAck?: boolean;
msgFactory?: (msg: IRMQMessage) => any[];
} If no name provided, route will be binded to default connection. Also global rmqService can be used for backward compatability. This way we can support multiple instanses of RMQ, multiple exchanges or queues and logic splitting. What do you think? |
The way I have decided to go about this was to follow the TypeORM nestjs module. TypeORM creates the connections via the Regardless of how we end up implementing this, it is good to know what limitations we have beforehand; We need to keep connections as global as it allows us to:
So really, no matter how I like to move most of the code to the The thing is, to capture routing and handle connections we have to have the channel in global space which contains the queue and the exchange that the queue is connected to. So what do we really have left for the And what really would we gain by separating these? If This all seems like this library can not really benefit much from One idea is to take the class(es) for routing as an argument to the forFeature(
{
exchangeName: "exchange",
queueName: "queue"
},
[
MyServiceRMQMicroservice,
]
), But it does add a lot of redundancy to the user's code if more than one module is going to use it, not to mention that usually, it is better to load configs used for Thoughts? Counter arguments? |
Not to mention that with neither this implementaion, or the one you proposed, we can offer the ability to join a queue in multiple exchanges. It might be useful to have a queue in multiple exchanges and if we are going to redesign the config here, we better find a way to provide this functionality too. |
I have looked into possible ways to get the providers defined in the parent module to allow for scoped configuration and routing for And even if we do, I am not sure how reliable it would be to tinker with the inner workings of NestJS DI like this. |
This is the best approach that I can think of to the configuration of this module:
With interface IRMQOptionsBase {
exchanges: IRMQExchange | IRMQExchange[];
queues: IRMQQueue | IRMQQueue[];
hosts: IRMQHost | IRMQHost[];
serviceName: string;
logMessages?: boolean;
logger?: LoggerService;
middleware?: typeof RMQPipeClass[];
intercepters?: typeof RMQIntercepterClass[];
errorHandler?: typeof RMQErrorHandler;
}
interface IRMQOptions extends IRMQOptionsBase {
alias?: string = DEFAULT_CONNECTION;
}
interface IRMQExchange extends Options.AssertExchange {
alias?: string = DEFAULT_EXCHANGE;
exchangeName: string;
messagesTimeout?: number = DEFAULT_TIMEOUT;
prefetchCount?: number = DEFAULT_PREFETCH_COUNT;
isGlobalPrefetchCount?: boolean = false;
intercepters?: typeof RMQIntercepterClass[];
}
interface IRMQQueue extends Options.AssertQueue {
alias?: string = DEFAULT_QUEUE;
queueName: string;
middleware?: typeof RMQPipeClass[];
}
interface IRMQHost {
login?: string;
password?: string;
host?: string;
port?: number;
vhost?: string;
url?: string;
reconnectTimeInSeconds?: number = DEFAULT_RECONNECT_TIME;
heartbeatIntervalInSeconds?: number = DEFAULT_HEARTBEAT_TIME;
} With NO With major refactoring of the explorer and structure of the project, this would be a viable solution for the library and I think is a good candidate for v3. All properties are in the right place and also allows the user to override one or more properties for one or more exchanges or queues and also removed the duplicate configurations, like durability. Not to mention that even tho the config interface changes, but it is far cleaner and also is still easy to migrate as it accepts both an array and an instance for connection, exchange, and queue configs. If a simple usage with a single exchange, queue, and the connection is needed, there is no need to enter additional parameters in Please share your thoughts about this approach for v3. It is not quite backward compatible, but migration is easy enough and isolated to one function. |
@falahati thanks. I'll think about suggestions and will return with my thoughts. Got hard schedule this week. |
@AlariCode which allows this library to be fully integrated into the already established nestjs microservice library. since this library is more feature-rich compared to the build-in rabbitmq client/server, rewriting this library to be compatible with nestjs microservices would be dope and also makes it quite easy for people that are migrating from the building rabbitmq driver to use it. consider it. |
@falahati the only problem is that we can't user module separation as other libs. I will considered using custom transport but this will change everything in lib and will be entirely different lib) |
well, it is not perfect, but it should cover most of the added features provided by this library. I have accidentally stumbled upon this when we had to convert our hybrid project to microservice, but since this library is not really microservice compatible had to change it to standalone. works no problem but in the middle of investigations that resulted in this conclusion I had stumbled upon the said article and was thinking about porting this to act as a custom transport, or maybe write my own library based on the code and experience of this project but as a transport. module separation is something that we already talked about here and I don't see a way to do it with the current lib either; so that part is not a problem. on the other hand, the fact that it forces the library to define clients and server separately saves us a decision on how to handle clients' registration. there is also build in naming support for clients, although we have to manually add multi-server support I suppose. in any case, just my two cents on this; figured that this might be of use in your decision. |
Hey @AlariCode! |
@maraero, unfortunately, I do not have time for further big features implementation due to other priorities. |
Hello, was this feature ever merged? |
This PR addresses #42, #44, and partially #20.
The idea with this PR was to change as little as possible of the code yet still allow multiple
RPCService
s to be available side by side. Following is the list of changes made to make this possible:RMQRoute
changed to include one or more service names to bindExtendedMessage
is edited to have the name of the service available.RMQInjectService
decorator was added to select a specificRMQService
instance based on the passed nameRMQExplorer
is now moved to a separate module so that we can initialize it only once and share it between differentRMQService
sforFeature()
method added to override the defaultRMQService
instance in a module, if more than one service is used by a module or ifRMQInjectService
is used to explicitly inject a specificRMQService
, this function is unnecessary as services are added in global space and can directly be used via theRMQInjectService
decoratorRMQGlobalModule
and anRMQModule
, even tho is not necessary for the library to work right now, it is useful for later expansion of the project (globally shared providers should be defined inRMQGlobalModule
and local providers in theRMQModule
)forFeature()
import addedTake a look at this and tell me what you think. There are a couple of places in which I have doubt if this is the best approach. In particular, I don't like the idea of having services in the global space and rather have them in per module scope. It would be preferable to have a connection manager defined in the global space with host connection information and then in module space we could have the actual services. This way, we can also share the
AmqpConnectionManager
. But this is a breaking change so maybe with v3 you can do this. I also don't know why we are adding meta data to theRMQService
and not keep them in a variable of theRMQExplorer
class instead. It should be faster than using theReflector
.Also, I don't like the fact that the emitter is used heavily in the library; I mean, it was already complicated for the purpose of the project but now with this additional code it is too complicated for my taste and I think separating it for each module might be a better idea.
I also hate the fact that unless for some reason the user of the library doesn't want to use the
RMQInjectService
decorator,forFeature
is almost useless. I mean we could remove it and features would still be the same. The only use for it is to change the defaultRMQService
for a module if and only if it uses a singleRMQService
.I also resisted the urge to change more of the code unrelated to the actual feature, but I think there might be room for improvement with the explorer and especially with cases like generating the regex string. In any case, these can be addressed in a separate issue and PR if necessary. But I had to fix some of the linter errors as false positives were making the development hard.
Please take a look into this and share your thoughts on it and the points raised above. If we can get this into the current version (maybe beta release) it would be great. I have a project blocked by this feature xD
I would be happy to allocate some of my time for version 3 to get it more pretty and clear in a new PR if you are ok with it.