lundi 13 juillet 2020

Is There a Better Way to Design This?

I’ve got a bit of a headache with my Fragment library. It essentially makes it easier for me to write code that’s decoupled from any framework (which allows me to port it quickly from one framework (say, Nest) to another (say, LoopBack))

It uses a lot of Domain Driven Design principles to accomplish this. But, at the end, you expose an Application Service, as well as some interfaces for services that need to be defined in the infrastructure (framework) and passed in to the Application Service (I.e. Repositories and Domain Services).

For example, if I had a MongoUserRepository class I defined in my framework that I need to pass in to a UserApplicationService, I do so at initialization, like this:

const userServive = new UserApplicationService(new MongoUserRepository());

Now, my library has this feature which allows you to register methods as event listeners. So, for example, in an aggregate class, I can register a listener for a UserRegistered event by decorating the method like so:

@On(UserRegistered)
private async foo(event: DomainEvent): Promise<void>{}

This makes it so when a UserRegistered event is emitted

await EventStream.publish(new UserRegistered(user));

the foo(event) listener will be executed.

So, here’s the problem. Event listeners are class-level methods. That is, there is no access to “this”. So, I am unable to access the passed in dependencies of the application service (first code snippet). That is, in my UserApplicationService class, I wouldn’t be able to do this:

@On(UserLoginAttemptsExceeded)
private static async lockUser(event: DomainEvent): Promise<void>{
const theEvent = event as UserLoginAttemptsExceeded;
const id = theEvent.user().id();
const user = await this.userRepo.findById(id):
user.lock();
await this.userRepo.save(user);
}

because the event listener doesn’t have access to “this” when it’s called by the dispatcher (when EventStream.publish(SomeEvent) is called).

The way I have the library designed, you can register listeners from anywhere in the domain. It could be from the Application Service, an Aggregate, an Entity, a Domain service. Or whatever. So, simply changing the decorator to use the “this” context wouldn’t work.

My current work around right now is to make all my repositories singleton and just pass in a delegate that’s defined in the infrastructure (framework). But, it’s not really as elegant as I’d like it to be.

It works. But, it’s not as elegant as I’d like it to be. Plus, it’s not dummy proof. Someone not familiar with design patterns would probably just hack their way around this...

Any advice for how I can possibly improve the design of this?

Aucun commentaire:

Enregistrer un commentaire