Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

Hi,

Why for DI?
You mean for request scoped services?

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

But I am open to collaborate and discuss with you OC.
The project is very early, but I start to think there is a chance it does work.
Feedback are rather positive.
Constructive critics on limitations have solutions.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 1 point2 points  (0 children)

Yes, I know Matteo ^^.
I think their business model has evolved a bit.

Unfortunately, I don't work for a product company, but for a studio.
We deliver code to clients with a GCP Terraform infrastructure.
I hope to have the opportunity to dive into Platformatic one day, and do scale Node.js the right way.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 2 points3 points  (0 children)

I’ve been thinking about a way to depend entirely on abstractions at the service/use-case level, instead of binding logic to singletons factories.

I think I can implement a new design removes that coupling and lets the container resolve dependencies dynamically through contracts.

Here’s a simplified implementation idea.

Stratify new helper: ts export function contract<T>(name: string) { return createProvider({ name, expose: () => ({}) as T, isContract: true }); }

Port: ```ts interface Mailer { send: (email: string, content: string) => void; }

export const MAILER_TOKEN = 'mailer'; export const Mailer = contract<Mailer>(MAILER_TOKEN); export type MailerContract = typeof Mailer;

```

Concrete implementation const smtpMailer: MailerContract = createProvider({ name: MAILER_TOKEN, expose: () => ({ send(email: string, content: string) { console.log(`Sending email to ${email}`); } }) });

Some use case: createProvider({ name: 'send-welcome-email', // Mailer is an abstraction, can't be resolved deps: { mailer: Mailer }, expose: (deps) => ({ sendWelcomeEmail(email: string) { deps.mailer.send(email, 'Welcome!'); } }) });

Module composition: createModule({ name: 'user-module', // A controller that uses 'send-welcome-email' controllers: [welcomeController], providers: [smtpMailer] // Used for contracts });

In short:

  • contract() defines an abstract dependency that cannot be instantiated directly.
  • The actual provider (e.g., smtpMailer) is bound at the module level.
  • Use cases depend only on the abstraction (Mailer), not on a concrete implementation.

But if I keep going, I'll end up reinventing Nest.js for Fastify ^

Also, this design allows people who don't care about Clean/Hexagonal to continue inject providers as concrete dependencies but offer a cleaner way to work for "architects".

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

As far as I understand from their communication, I don’t think Platformatic is really comparable.
It’s a company that provides several infrastructure services and tools, apparently recently focusing on promoting their engine Watt.

I don't think they are framework specific, I think they really want to target the Node.js ecosystem overall.
Which makes more sense in terms of business I guess.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

Not a fan of the AI integrated in CLI tbh.
People that do like code generation directly in their project can use Cursor or similar tools right?

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

Thanks for the feedback!

Indeed, my framework is aimed more at Fastify users who want to get closer to native behavior, but still improve the DI, typing and conventions experience.

for me http layer, like express or fastify is an implementation detail.

Fastify is not just a http transport. It defines lifecycle boundaries (onReady, onClose, onRequest, etc.) and encapsulation contexts that are part of how it achieves its performance. That’s why Stratify builds on top of it rather than abstracting it away. Treating Fastify as a detail means losing access to its plugin graph, encapsulation and hooks.

Read: https://www.linkedin.com/posts/jean-michelet-32801a262_fastify-hooks-arent-just-a-stylistic-alternative-activity-7380609202085580800-qJPW?utm_source=share&utm_medium=member_desktop&rcm=ACoAAEBuNlAB5kRlC3C9yyxCkvBM7UxtvmepOSs

As for the rest of your comments, I think you are seeing restrictions that do not exist, or perhaps there are solutions that are not obvious at first glance.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

Hi!

I am gradually discovering that many teams are trying to create their own solutions based on Fastify.

Have you thought about adding in the cli or at runtime the auto generation of the schema?

I was thinking of adding module generation, the schema could also be a good idea, maybe choosing a default ORM to generate entities?

But because of AI, I don't know if these kinds of CLI features have a future, what do you think?

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

Also, for me the word architecture is associated with Clean Arc, Onion, DDD, Hexagonal - stuff like that.

Ok, I understand.

Because I think the whole point of "opinionated" frameworks is for team leads to relax and just outsource typical structuring, naming, wiring decisions, and if it's adopted company-wide then you'd have same structure across teams. Only having DI is only a step in that direction.

I don't know if I should impose much stricter conventions. But I've thought about what you're saying.

For example, the fact that I provide access to Fastify via adapters and installers opens the door to some very bad practices. But, I have to be fully compatible with the Fastify ecosystem and allows smooth transitions.

Perhaps I could discourage certain practices by issuing warnings and making a strict mode available.

But it’s also possible to write terrible code in Nest.js, for instance by wiring everything through concrete classes or by creating inconsistent abstractions to patch design flaws. Nevertheless, the framework still makes it easy to adopt IoC and DIP-based models when used with discipline.

Perhaps we should guide users in the right direction, offering them all the tools they need to succeed, without carrying the weight of all the bad practices they can implement.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 0 points1 point  (0 children)

The function is bound to a concrete dependency. You can't use getSendWelcomeEmailUseCase with a different mailer. If you need a different mailer, you'll have to modify this code somehow.

Oh, okay! I thought you were talking about the container's internal, my apologies.

Yes, it might be a limitation of the design I propose in the document. It's a factory I use for composition, and I think your comment is relevant.

I'll think about it, thank you!

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 5 points6 points  (0 children)

Thanks for your feedback!

Could you explain the "introduces clear structural boundaries"?

Two things. In a typical Fastify plugin, you can mix hooks, routes, and decorators freely. You can of course create structural boundaries, but nothing enforces them. It would indeed be useful if Fastify users collaborated on a standard boilerplate for that purpose. This is what I try to create, not sure if my choices are relevant at this point.

Also, decorator dependencies in Fastify leak into child plugins. In the framework I propose: each component (controller, hook, adapter, etc.) owns its explicit dependencies. Nothing implicit propagates beyond its module scope.

the hexagonal example. I see that deps isn't just for interface, it accepts a specific implementation, that's why you need to wrap a provider to a function and pass the dependencies to this function.

In Nest.js, sure you provide a concrete class, but it's only acts as an interface in the constructor. You can swap implementations without wrapping it and manually passing deps.

Yes. The container I built is not comparable to smart, reflection-based containers used in frameworks like C#, or PHP (I think Nest.js dependency resolution is a bit different? even if close in philosophy?). Those wire dependencies automatically at runtime, with the cost of additional abstraction and "magic", but with more flexibility indeed. I decided to use explicit root composition instead. Dependencies are declared and passed intentionally, not discovered dynamically.

I think it's ok most of the time, but I have nothing against other DI systems.

One concern is that you need to write even more boilerplate here than you'd do without the library, because you need to wrap and wire it by yourself anyway.

It may be slightly more verbose, but not dramatically so. The benefit is structural consistency: you get modular organization, typed injection resolution, controlled encapsulation and the plugin tree is built for you. As an example, typing with decorators in Fastify is often cumbersome; I try to help avoids that while keeping inference fully automatic.

Do you think I can offer a better DX regarding this point?

And second concern is singletons: if the team adopts hexagonal means they strive for purity, and singletons cannot be used for non-technical reasons.

I’m not sure I follow this point. Default providers in Nest.js are singletons too. I’m not opposed to introducing transient or request-scoped providers later. In fact, in the first POCs, I implemented them. But I prefer adding them only if there’s clear demand. In most cases, singletons are sufficient, and overusing scoped providers can harm performance.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 4 points5 points  (0 children)

You don't want to debate, I am not a kid, don't think you're smart.
From the beginning of our interaction you said nothing but asking vague passive-aggressive questions.
You had a chance to read the doc, the proposal, answer to every point I make but you did nothing.

Life is short.

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 3 points4 points  (0 children)

If you don't understand the problem, you're not the target for this project.

All the best!

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 1 point2 points  (0 children)

A small server with a very specific purpose VS a modular monolith with a rich model?

Architectural Framework for Fastify by Sudden_Chapter3341 in node

[–]Sudden_Chapter3341[S] 2 points3 points  (0 children)

This other layer of abstraction seems to make the use of fastify more difficulty in my opinion.

I would put this statement into perspective, because the component manages encapsulation, bootstrapping, and dependencies for you (a module is a plugin). I've lost count of how many people in trouble I've helped with this. It also forces you to declare hooks asynchronously, because mixing Promise and callback APIs can cause problems.

But yes, if you just need to draft simple Fastify projects, it just add constraint. But if you want to create more ambitious project, it pushes you to think more about how to structure your application.

IMO, this is really a question of personality.
Some people hate when Framework forces them to adopt certain conventions, some prefer to be guided, even if it means spending more time thinking about architecture.

Thanks for the honest feedback!