-
-
Notifications
You must be signed in to change notification settings - Fork 17
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
Improve architecture #52
Comments
Ping for feedback: @climba03003 @melroy89 @Fdawgs |
I'd keep it as |
I fully support this initiative to reorganize the codebase, and i can heavily help on that. |
For |
You can take inspiration from this repo created by Matteo.
https://github.com/mcollina/modular_monolith/tree/main/modular
…On Sat, Nov 2, 2024, 15:20 Mouad ***@***.***> wrote:
I'd keep it as routes and plugins as then the language is consistent with
how the official Fastify documentation and plugin docs refer to these
concepts, which should make it easier to understand.
For routes, certainly easier to understand since it's standard/present on
most demos.. Imo it is not really representative of the real purpose of the
components inside.
—
Reply to this email directly, view it on GitHub
<#52 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/A2J2324KOIZYYSDCNDAX4QTZ6TNRZAVCNFSM6AAAAABRBXKBWGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINJTGAYDMMBWGE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
For the project structure that I currently using,
Basically, the domain you mention is scoped inside the route, and you can follow the routes name to find which files you need to edit. The actual business logic should be inside |
Weird, I see you contributing to it, why? |
I think if this PR is accepted, we should implement the changes here. |
My setup is currently looking like this (and yes I'm using autoload for sure): tree -d src
src
├── handlers
├── interfaces
├── plugins
├── routes
│ └── api
│ └── v1
└── schemas Autoloads are used for I call it My The file (index.ts) in the Then again, in my case Fastify is used as backend. The handlers are only doing database calls. If you have much more logical in the handles, I would advice to split that further. But again, in my use-case this level of abstraction is sufficient enough. DB Pagination, advanced DB queries or payload validation is all done with plugins like |
Fastify community is too anarchic, we should create a |
IMHO I don't yet know all the technical details of Fastify's internal workings, but integrating it as a component of the network layer in a domain-driven architecture shouldn't be an issue. I see two levels of improvement and separation of concerns: at the folder and file level, and at the module (code) level. From an architectural perspective, it would be preferable to organize endpoints into domain-specific folders, each containing its own use cases. This approach would encapsulate the technical details of Fastify's network layer. The controllers, consumed by this network layer, will receive the database client through dependency injection, to avoid coupling the domain with this type of detail. Looking at the testing perspective can be interesting. If I understand correctly, the philosophy is that, we do not mock the external services like database, so the controller layer can serve as an orchestrator At the code level, it might be beneficial to create entities, only if the business complexity increase and or if we want to test the business rules without relying on the database, this declarative approach also allows us to see things more clearly but brings complexity in the development because you have to be familiar with this model This doesn't prevent us from maintaining the philosophy of systematically testing use cases with database persistence. The first step could be to organize the folders to separate the network layer from the domain by creating the first controllers and evaluate if the transaction script model is sufficient. From there, we could start creating the first entities. I welcome any other opinions.
|
@Mathieuka IMO for a demo, I believe wrapping each 'action' (command or query) in a separate use case is excessive. This adds another layer (so + mental load and "complexity")—especially for something meant to be "straightforward". As @jean-michelet mentioned, Fastify lacks built-in dependency injection, but (correct me if i'm wrong) we can achieve a similar effect with the plugin mechanism. Using Fastify plugins, we can consider services like plugins, so we can injecte then into controllers via routes that have access to the app instance. We could also instead of separating each route into its own file, consolidate all routes in one file per domain and handle separation at the controller layer or not at all. However, I believe keeping a file per route still has its advantages. It gives us a clear, quick overview of each domain’s actions and makes it easy to locate and modify specific routes as needed. Other thoughts on this approach?
|
For a demo intended to be "simple," I agree that the complexity might indeed be unnecessarily high. I don't yet know the plugin mechanism well enough. Consolidating all the routes into a file by domain and managing the separation at the controller level seems like a good idea. This approach mitigates complexity and offers interesting cohesion from a readability standpoint. |
My question is whether controllers, routes, and services should be part of the domain. Shouldn't they belong to the application layer instead ? The domain should be self-sufficient through its modules and entities, if necessary ? My point is that if we want to bring about the separation of responsibilities, it will be done through the entities, otherwise we will end up with the same problem of controllers who manage the state, apply the business rules, and persist the data.
However, the trade-off you propose might be sufficient for the needs of the demo |
This is not what I said, I said it could be improved. A lot of developers think about dependency injection trough constructor injection: @Injectable()
class DateFormatter {
format(date: Date): string {
return date.toISOString();
}
}
@Controller('some-prefix') // prefix routes path defined in the class
class AppController {
constructor(
private readonly dateFormatter: DateFormatter,
) {}
@Get('/log-date')
logCurrentDate(): { message: string } {
const currentDate = new Date();
const formattedDate = this.dateFormatter.format(currentDate);
return { message: `${this.prefix} ${formattedDate}` };
}
} But the same is achievable via a functional paradigm: function formatDate (date) {
return date.toISOString()
}
fastify.register(function (instance, deps) {
instance.get('/log-date', (request, reply) => {
const currentDate = new Date()
const formattedDate = deps.formatDate(currentDate)
reply.send({ message: `Format date: ${formattedDate}` })
})
}, {
formatDate, // Function injection
prefix: '/some-prefix' // prefix routes path defined in the plugin
}) |
Totally agree with you,all actual components that i cited should reside in the application layer. In my example, I intended 'domains' simply to represent areas of work or functionality, rather than fully self-sufficient domains.
Ideally, controllers won’t manage any of this. This should be handled by the service layer, and I don’t think the demo has enough business complexity to introduce entities as it add another another layer.. However, as you mentioned, I don’t think this level of separation is necessary for the demo, especially since it’s an "introductory" demo (I guess xD). So it should be accessible for all potential users, who may find to much layering / separation overwhelming or complex at this stage. |
@Turn0xx fully agree about the demo has not enough business complexity to go in this direction I took the time to read the plugin system documentation and I understand your arguments better.
|
I think a more modular approach could be beneficial, and it will be easily achievable when the PR I mentioned in this comment will be merged. Example of what could be achieved: export const deepPluginDependency = createPlugin((instance) =>
instance
.decorateReply('deep_dependency_reply', true))
export const pluginDependecy = createPlugin((instance) => instance
.decorateRequest('dependency_request', true), { dependencies: [deepPluginDependency] })
export const dependantPlugin = createPlugin((instance) => {
return instance.get('/', (req, res) => {
expectType<boolean>(req.dependency_request)
expectType<boolean>(res.deep_dependency_reply)
})
}, { dependencies: [pluginDependecy] }) |
It would be plugin injection, and the injected plugin decorate the fastify instance, request and reply with features in the scope it is injected (encapsulation). |
Currently, we have a couple of problems:
It is ok for a very small demo, but it becomes inconvenient to maintain if we add more features.
I think controllers should be dedicated to HTTP handling (e.g., request/response handling, parameter validation...).
We can rename the
plugins
folder to something more representative, likeinfrastructure
orshared
, to reflect that these are reusable components providing core capabilities across the project.Maybe we should rename
routes
todomains
ormodules
to represent distinct business areas (e.g.,auth
,tasks
, etc.).Wdyt? Do you want to work on it?
The text was updated successfully, but these errors were encountered: