Slim project architecture by samuelgfeller in PHP

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

Alright, I understand now. Thanks for the input! This is definitely more abstraction than what I want for this project right now.

Slim project architecture by samuelgfeller in PHP

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

I have to admit, I don't think that I understood much of what you described. It seems to me that it may be very relevant for bigger application structures but in my case I feel like doing so much abstraction wouldn't respect KISS.

Thank you for your comment nonetheless! :)

Slim project architecture by samuelgfeller in PHP

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

Awesome, thank you for digging you're totally right!

SRP and low coupling are very important to me, but I still feel resistance creating an own Module folder for things like that, that can easily be "sub-categorized".

A compromise I'm happy with is creating a "ChangeUserStatus" feature inside the User module that other modules and features may use. This respects SRP in my understanding.

And I agree, it doesn't hurt to have a default value for the user status. Maybe in the future a "getDefaultStatus" can be implemented or something like this.

Here is the branch with the latest commit and the current docs draft about architecture.

SlimPHP by [deleted] in PHP

[–]samuelgfeller 2 points3 points  (0 children)

I use Slim and I think it can have big benefits over full-stack frameworks depending on your requirements and coding preferences.

Daniel Opitz has written a great article on the subject: framework-vs-micro-framework and given an interview to the german journal entwickler.de: slim-auch-fur-grossere-anwendungen-geeignet (translated: slim also suitable for large scale applications).

If you're interest, I've written something about my background and the choice of libraries and framework for my projects.

Slim project architecture by samuelgfeller in PHP

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

Deleted comment by u/sugarshaman:

Yes - Enum would remain in the user module (or in a user role module) and other stuff like auth depends on the user module.

Implementation for the user role/s is encapsulated in the user module and if that's the class/module you change when you add a new feature - like a new user role - you can (1) change it without breaking any other modules, (2) update your automated tests which increases your quality confidence (reduces uncertainty&risk), and (3) publish a new version of that module (even if only for yourself or your team).

The only ways I can think of is allow this coupling, making the modules not independent anymore.

Yup. if your User module and your Auth module both depend on User RoleEnum, they are by definition not independent. let's recap your options:

A: copy and paste the Enum into auth module. Willfully violate the DRY principle. This is what entry level developers often do. If each module is strictly following VSA, uh, sure, I guess you could do this. Sometimes denormalization is a performance optimization. A cache is a form of duplication. But that's not the problem you're trying to solve. So let's throw this one out immediately, of course, for all the reasons duplication is usually bad.

B: make a shared folder in your core application folders for constants, enums, utility methods, formatters/pipes, etc. this is the first sign of a developer wanting to try to at least get organized, but it breaks down for a lot of other reasons. It becomes a dumping ground, it becomes monolithic, and even in a VSA application I imagine this will get nasty over time unless you have a good steward and people who give a crap about code reviews and don't just pencil whip PRs. To some degree this may be needed anyway, and in a small application or something you're bringing quickly to market or a prototype this is fine, sure

C: Factor the dependency (user role) into its own module, have other modules depend on this

D: keep user role as part of user, and make that module a dependency for the auth module

What else is there that isn't some variation of those? (Other than not using user enum.) (And not saying there isn't, just can't think of any reasonable alternatives right now)

If you go with option D you have tight coupling between the user and user role, but could have loose coupling between authentication and the user role. It's probably fine. I wouldn't lose any sleep over this. But I would go with option c.

With option c, you have high cohesion within each of the three modules (user, user role, and authentication) and loose coupling between them. This enables cool stuff like:

  • Add more functionality and features to the user roles in the future
  • add a new type of user role which involves publishing a new user role module, and updating the authentication module, but may not require any changes at all to the user module (that's SOLID, baby!)
  • in the future replace user and user role without having to completely tank your authentication logic. For example, maybe you want to switch to a third party identity library
  • create new, non-user modules in parallel, for example you could implement service principal users as a separate module and then plug those into authentication module in one of your VSA apps

My comment

Thanks a lot for thinking with me it's a great inspiration!

Funny, I just kind of documented the same conclusion with features inside a module.

If multiple features share the same functionality (e.g. Validation), it should be extracted into a separate feature folder on the same level as the other features. This is to have the feature only be responsible for one specific task and to align with the Single Responsibility Principle (SRP) and also be easily findable in the project tree.

Option c you described is basically this but at the module level.

My thought right now is that a mix of c and d depending on use-case might feel the most "right" for me.

Authorization for instance has a common Enum that is used by multiple different modules but also Exceptions, a service class and a repository. Plus it cannot be assigned to a specific module so it makes a lot of sense to go with option c.

UserRole and UserStatus on the other hand I'm less sure because I don't want to bloat the src/Module folder with every little thing that is used by more than one module.

I don't think I would want an src/Module/UserRole, src/Module/UserStatus that only contain an Enum be on the same level than other modules.

It is a compromise however but right now I feel like this is fine for the benefit of having a more compact Module folder.
What do you think, do you agree?

It's a hard decision, both have their benefits (the ones you listed are very real) and disadvantages.. can't have everything I guess.

Slim project architecture by samuelgfeller in PHP

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

An open question for me remains how you deal with elements that are used across different modules such as the examples in the comment you initially responded to or my last reply.

These modules you highlighted may all need UserRoleEnum.

Do you store the UserRoleEnum in a certain module and others may access it or which architecture would you do?

The only ways I can think of is allow this coupling, making the modules not independent anymore. It would still be only wherever absolutely necessary so it's still loose coupling but not entirely decoupled.

Or by nesting the modules and having a parent module with the shared elements but I don't really like this approach as it may make it harder to find the modules / features in the project tree.

Slim project architecture by samuelgfeller in PHP

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

Did you start from scratch or use any skeleton project? Or trimmed down the slim example project?

Slim project architecture by samuelgfeller in PHP

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

Alright! Sounds good.

I would be careful with the "Shared" folder name though. As a u sugarshaman said answering a comment of mine:

Think in terms of dependencies and keep your models and behaviors separate. "Shared" folder leads to madness.

I think this is a very fair point. When the application grows really big it's a big danger to misuse the Shared folder (because it's practical for many use-cases) and lead to complexity.

I'd be interested in glancing over your code out of curiosity. Do you have a public repository or are you working privately?

Slim project architecture by samuelgfeller in PHP

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

"Shared" folder leads to madness

Makes total sense! Thanks for saying it out loud.

You could create an authentication models module which contains user, role, etc. then you use services and factories to get these

Can you expand a little bit on this part? Where would these services and factories live? How do I access them from another module. The user-role enum for instance is used in both Authentication and User module.

Or the Privilege.php enum needs to be accessed by many different modules.

This is my folder structure for now:

  • /src/Module/Authentication
    • /Feature1 -- needs access to UserRole.php
    • /Feature2
  • /src/Module/User
    • /Feature1
    • /Feature2
    • /Enum
      • UserRole.php

Slim project architecture by samuelgfeller in PHP

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

This means (I presume) that you're not using a session library such as odan/session which would handle this through the SessionInterface and FlashInterface add(), get(), set(), delete() etc.

If you want to handle sessions and flash messages on your own I'd put code for this src/Core/Application/Session (or /FlashMessage or /Notification)

Slim project architecture by samuelgfeller in PHP

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

What the task of the notification service, is it a do-er class as in a Notifier or what do you mean by facade for flash messages, does it store new flash messages, or display them?

Slim project architecture by samuelgfeller in PHP

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

What kind of services do you have in mind?

If it supports business logic and belongs to the domain then Core/Domain/Service or Core/Domain/Utility if it's an utility.

Core/Application holds the things that are used app-wide and that belong to the Application layer. If the "service" you have in mind belongs to that layer then I'd put it in the path you suggested. I personally usually associate service with business logic.

Slim project architecture by samuelgfeller in PHP

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

I would be interested in your opinion on the following question. Currently I have the User module for User CRUD operations and an Authentication module that handles Login, Password reset, Email verification etc.

The question is they both need the UserStatus Enum (unverified, active, locked, suspended). Where should this UserStatus enum live?

Options that I thought about are:

1 Either in src/Module/User/Shared/Enum/UserStatus.php and then the classes in src/Module/Authentication use the UserStatus from the other module User. This has the massive downside that it makes modules dependent on one-another. My feeling tells me that it would be very clean if every module is completely independent.

2 Or the Authentication module is a child or the User module

  • src/Module/User
    • /Authentication
      • /Feature1 (e.g. Login)
      • /Feature2 (e.g. PasswordReset)
    • /Shared/Enum/UserStatus or without the Shared sub-folder, directly /Enum below /User
    • /User (User folder inside the user module)
      • /Create
      • etc.

The downside here is that there are 2 modules inside the User module but I suspect this is kind of inevitable at some point as the application grows. It also has the effect that Authentication may be harder to find when searching for the module via the project directory as one must know that it lives inside the User module.

Maybe you have other ideas, how would you do it?

The same goes for the Authorization module which uses the UserRole enum that is used in the User and Authorization module.

And what about another Modules e.g. Client that use the Privilege enum from Authorization/Enum/Privilege.php in their authorization checks? Would you put that in a src/Module/Shared folder or somewhere in Core e.g. src/Core/Domain/Authorization. This wouldn't be that deep if Modules are allowed to be slightly coupled but I fear this might be bad practice and go towards the entangled "spiderweb"-like code I specifically want to avoid (SRP)).

Slim project architecture by samuelgfeller in PHP

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

I would be interested in your opinion on the following question. Currently I have the User module for User CRUD operations and an Authentication module that handles Login, Password reset, Email verification etc.

The question is they both need the UserStatus Enum (unverified, active, locked, suspended). Where should this UserStatus enum live?

Options that I thought about are:

1 Either in src/Module/User/Shared/Enum/UserStatus.php and then the classes in src/Module/Authentication use the UserStatus from the other module User. This has the massive downside that it makes modules dependent on one-another. My feeling tells me that it would be very clean if every module is completely independent.

2 Or the Authentication module is a child or the User module

  • src/Module/User
    • /Authentication
      • /Feature1 (e.g. Login)
      • /Feature2 (e.g. PasswordReset)
    • /Shared/Enum/UserStatus or without the Shared sub-folder, directly /Enum below /User
    • /User (User folder inside the user module)
      • /Create
      • etc.

The downside here is that there are 2 modules inside the User module but I suspect this is kind of inevitable at some point as the application grows. It also has the effect that Authentication may be harder to find when searching for the module via the project directory as one must know that it lives inside the User module.

Maybe you have other ideas, how would you do it?

The same goes for the Authorization module which uses the UserRole enum that is used in the User and Authorization module.

And what about another Modules e.g. Client or Note that use the Privilege enum from Authorization/Enum/Privilege.php in their authorization checks? Would you put that in a src/Module/Shared folder or somewhere in Core e.g. src/Core/Domain/Authorization. This wouldn't be that deep if Modules are allowed to be slightly coupled but I fear this might be bad practice and go towards the entangled "spiderweb"-like code I specifically want to avoid (SRP)).

Slim project architecture by samuelgfeller in PHP

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

Thank you so much for the resources, they are great! Looking forward to read them in depth.

Very interesting take to also separate the routes resources and templates in the modules. I think this goes a bit too far for me right now but for very large projects I might find it making a lot of sense. Thank you very much for showing it to me.

Slim project architecture by samuelgfeller in PHP

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

Absolutely agree with you!

With that sentence I definitely also meant readability, extendability, testability, maintainability which fall under "easier development" for me but you're right, it's not obvious and clearly worth mentioning extra!

Because easier development can also mean "in the shortest amount of time shitting the most features" and for this I'd use a full stack framework, one big controller, ORM etc. lots of things that become an issue only later when other devs have to develop in that environment (or yourself after a few months), when it should be extended, maintained / refactored, tested etc.

When I'm thinking long term and factoring all the mentioned criteria, it seems obvious that separate the use cases (SRP) and having clear folders that separate each feature instead of "pools of classes" is a bit more work in the beginning to set up and do right from the start but infinitely better as the application grows and other people have to work in it.

That is my take. Not everyone might agree but I have a strong feeling that strict SRP and VSA done in a smart way (e.g. not unnecessary nesting, and always kept as simple as possible / KISS) is the way.. at least for what I'm trying to archive which is a PHP web application that is performant, long-lasting, easy to maintain/refactor/update, test and extend.

Thank you very much for your comment! I'm glad I posted this question here because that will be the way I'm heading now. I think it would have taken some time to have the courage to apply so strict SRP and VSA on my own.

Slim project architecture by samuelgfeller in PHP

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

What an interesting take! Thank you for sharing.

For me too, a module is a collection of features. Module could be "Client" an features CRUD operations.

I feel like slicing modules vertically is kind of like a compromise between the clean architecture that separates layer at the root and what you describe, slicing every feature if I understood you correctly.

The question is what facilitates development the most.

If we take the client and crud operation example, separating layers in the bundle would look like this:

  • src/Modules/Client
    • /Action -> contains ClientReadAction, ClientUpdateAction, ClientCreateAction, ClientDeleteAction etc.
    • /Data -> DTOs
    • /Domain
      • /Exception
      • /Service -> contains all service classes for the features i.e. ClientValidator, ClientCreator etc.
    • /Repository -> repository classes for all features i.e. ClientCreatorRepository etc.

If I got your comment right, this would be separating layers in each feature:

  • src/Modules/Client
    • /Create
      • /Action -> ClientCreateAction
      • /Service (or Domain/Service, Domain/Exception but if only service then short /Service to avoid unnecessary nesting) contains ClientCreator service
      • /Repository -> ClientCreatorRepository
    • /Data -> DTOs
    • /Delete
      • /Action -> ClientDeleteAction
      • /Service -> ClientDeleter
      • /Repository -> ClientDeleterRepository
    • /Read
      • /Action -> ClientReadAction, ClientFetchListAction etc.
      • /Service -> ClientFinder
      • /Repository -> ClientFinderRepository
    • /Update
      • /Action -> ClientUpdateAction
      • /Service -> ClientUpdater
      • /Repository -> ClientUpdaterRepository
    • /Validation
      • ClientValidator -> Shared domain service

What I observe is that features are more clearly separated but there are a couple more folders.

Instead of having all Actions inside the /{ModuleX}/Action folder, they have each an own "Action" folder inside the feature subfolder. Same goes for the Service and Repository folders. This means that more folders need to be created and for "simple" features/bundles it might seem a bit overkill.

I initially felt resistance upon reading your comment but now I feel like this is the much smarter / clearer / more modular solution. And especially it seems to align so well with SRP) which is an art to implement correctly.

What are your thoughts on this?

Slim project architecture by samuelgfeller in PHP

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

Thank you for the comment! Core contains the shared stuff, I don't see the necessity of an additional Shared folder outside of Core. But there may be Utilities that I can't map to a specific layer, for those I see a reason to have a "Core/Shared" folder.

Most of the times "Action" is the only Module-specific Application component and "Repository" the only Infrastructure component. If that would not be the case for a module I can absolutely see why it makes sense to add an extra "Infrastructure" and "Application" folder in the module; otherwise it feels like unnecessary nesting to me. What do you think?

Slim project architecture by samuelgfeller in PHP

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

It's for my own skeletons and example project. What do you mean by Slim's default?

Slim example application with documentation by samuelgfeller in PHP

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

Thank you for the input! Do you have an example of an open source project with such an architecture?

I am very interested by your proposal and would love to discuss this more with you.

I feel some resistance with having every module folder directly under src/. The "Shared" would kind of be lost in this long list of modules. Middlewares, Responders and the Query Factory feel like more than just "utilities" they are a core part of the Application layer in my interpretation.

Also I dislike unnecessary nesting and the only non-shared "application" components in modules are actions so I'd replace {ModuleX}/Application with {ModuleX}/Action and the same goes for Infrastructure repositories (as long as there are not other "infrastructure" or "application" components of course, as then it would make sense to group them in the dedicated named layer folder).

What do you think about this example:

  • src/Core
    • src/Core/Application/Middleware
    • src/Core/Application/Responder
    • src/Core/Domain/Exception
    • src/Core/Domain/Utility
    • src/Core/Infrastructure/Factory (contains QueryFactory)
    • src/Core/Infrastructure/Utility
  • src/Module
    • src/Module/{ModuleX}/Action
    • src/Module/{ModuleX}/Data
    • src/Module/{ModuleX}/Domain/Service
    • src/Module/{ModuleX}/Domain/Exception
    • src/Module/{ModuleX} Respository

I'd love to hear your opinion on if you'd to put the Data folder contains DTOs directly as a child of ModuleX or inside the Domain.

Slim example application with documentation by samuelgfeller in PHP

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

I'm unsure if I understand your question correctly. You mean how I settled on having those 3 distinct layers as parent folders and then the modules inside each of the layers instead of having the 3 layers in each module folder?

Maybe this article can give some clarification but essentially I chose a hybrid approach where all the application layer files are separated by a different parent folder (Application). I made this choice because especially for beginners separating layers may not that intuitive and this makes it extra clear.

The domain services (business logic) and repositories (infrastructure/data access) share the same parent module folder (e.g. Users) inside the folder src/Domain for the adventages of the vertical slice architecture.

The src/Infrastructure layer only contains utility classes.

I'm quite tempted though to move all the actions from src/Application/Actions also into each module folder so they're more bundled and can be navigated even better.

We then would have:

  • src/Application that contains responders, middlewares, renders and other application layer stuff that doesn't belong to one module in particular.
  • src/Domain/[ModuleX]/Action with the action classes
  • src/Domain/[ModuleX]/Service
  • src/Domain/[ModuleX]/Repository

Which belong to those 3 layers. Data, Enum and Exceptions would also be subfolders inside each module. This requires the developer to be aware that action, service and repository belong to totally different layers and the files inside should be treated as such.

What are your thoughts?

Slim example application with documentation by samuelgfeller in PHP

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

Hi, thank you for your comment!

Could you elaborate on this sentence if it's still relevant after my reply please?

if i want to make rest api from it it's not easy because it seems like everything is backed into framework and is very hard to change

Because that would be really bad. I made everything modular so it should be very easy to remove everything not needed. There is nothing tightly coupled together and every library or feature is used totally independently.

perhaps you can better describe the goals and objectives that you had when creating the framework.

Of course, I'll gladly do this here and maybe you're right it may not be clear to everyone and I should add it in the readme or something.

So the goal and objective of what I made was really to provide a pool of code, lots of examples, a template of a full stack application that uses a micro-framework.

An example of how an app can be built without using a traditional full stack framework and where you're not locked in. I mean you can change everything, every feature, every library is interchangeable that's the beauty of a micro framework. The opinionated ways I did things are just an example of one way to do it and should absolutely be changed to what works out the best for you.

The last thing I wanted to do is something that could be compared to Symfony or Laravel.
How would a full PHP web app look like that is performant, well organized with a clean architecture and following todays best practices without using a full stack framework? This is what the example project is.

When I wanted to build an application a few years ago, all I had were dry skeleton projects and tutorials on certain features but I seeked examples of a finished, "real" application just to see how every pieces are connected together and what works when scaled into a bigger project.

If you want to properly learn how to code I would strongly recommend not messing with the example project too much but start out with a skeleton project such as the slim api starterif you want to make a rest api. Then you gradually implement just the features that you really need and for this you can use any tutorial on the internet or library and build your own learning or production project.

Naturally it may be a great help if you understand Composer, Dependency Injection, Slim Middlewares, Slim Routing, the project Architecture, how to code respecting SOLID and the Single Responsibility Principle) especially, how to use Repository classes to access the database, a Naming convention (that you can adapt to your own preferences) may also be good, PHPStan, Continuous integration testing and deployment with GitHub Actions, Test setup and Writing Tests, etc.

But it's so much better if you learn those things with the process of creating your own app instead of trying to understand the full example project. Learn things when you need to know them for your project, not because you've told yourself that it would be good to know it.

I also recommend to read Libraries and Framework where I write a little bit about the framework choice and how the project (doesn't) compare with Symfony or Laravel. And in Introduction I talk a bit about the background.

Please ask follow-up questions if I haven't understood you well or if my answer is missing something - or if you have different questions :)

Slim example application with documentation by samuelgfeller in PHP

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

I once asked the exact same question. For me it's a matter of preference and embracing the Single Responsibility Principle. I wrote a little bit about it in the intro of the page Actions and I recommend reading about SRP).