I’ve seen a fair few projects make a distinction between “Services” and “Modules.” Should I bother with this? If so, what determines this division, and (how) do they interact with the rest of the codebase differently from one another?
In my understanding and use cases, services are the core of the game, each controls a specific general part of the game and handles everything related that aspect, while modules are used to help the services do the logic. Lots of times modules are more general purpose and self-contained and can be used by multiple services or scripts, sometimes they just simply hold values in tables without doing any operations by themselves (allowing other services/modules to operate on that data or just read from it)
I think knits documentation can be helpful with some examples, I think slietnick also has some video tutorials on it with better use cases.
(note that in knit “Controllers” are just a different name for “Services” for the client)
The Knit example docs don’t do a great job of demonstrating the differences, but I get what you mean. If I were to start differentiating, I would probably define services as scripts that execute the game, and modules as (mostly) just libraries used by services.
My understanding is that modules are for reusing code across multiple services, and services are just singletons.
In the setup I’m currently working with, I use the ___System naming convention for self-managed services (always Scripts) and ___Module for supporting classes and other libraries (always ModuleScripts). On the client, LocalScripts that self-manage are ___Handler
From my perspective, these factors decide if something should be a System or a Module:
- Is the code written to manage a component of the game, aka is it the responsible actor (the backpack system that directly interacts with other game systems) or is it a supporting actor (the backpack management utility that only interacts with the backpack system, not with any other systems)?
- Will this piece of code be useful for a single game system, or will multiple systems be interacting with it? (If multiple systems interact with its intended purpose it should probably be a module that multiple systems can require. But if it is only intended to be uses by a single system, it would not be a module. It can still be a modulescript!! But it would be parented directly to the system script, not the framework-wide module folder.)
- Is this system highly specific to this game, or should it be written to be easily reusable in the most generic way possible? It will probably still be a System instead of Module, but this is important to keep in mind while writing it.
If I look through my game hierarchy right now, 95% of modules are supporting libraries, shared data tables, and very game-specific components like projectiles that are embedded in various systems (sharing calculations, validating input of other systems and the player).
I’m very much against the setup of writing every single bit of code in a ModuleScript and requiring it somewhere, and much prefer the approach where each system is neatly fenced off with the API to interact with it clearly defined through BindableEvents/Functions. Doing it this way has allowed me to write complex projects where every piece of code can be changed without needing to crawl through the web of other pieces of code that interact with the same modules. And it comes with the bonus that you can yoink any system out of the game and add it to another.
To me, the difference is that modules don’t do stuff on their own, whereas services do. ‘Module’ is synonymous with ‘library’. I don’t know if modules can be stateful or not, but I’ve never considered something that is stateful to be a module, so probably not—although this could just be a side effect of how I personally choose what should be a service and what should be a module. I’m also not sure if functions inside modules can change (or even read) the state of things other than what was passed to them and themself, but I’m leaning towards ‘no’.
Both services and modules can be either ModuleScripts or Scripts/LocalScripts and both need a public interface.
Out of interest could you clarify how this differs from using a ModuleScript? Looking at the screenshots you sent it looks like these could be put inside a ModuleScript as members of the returned table. I guess the only benefit I can think of is that scripts run on their own and don’t need to be required like you mentioned, but are there more?
The key difference is that these scripts run on their own, and other systems and modules can connect to them without the need to require them.
This is important because a modulescript needs to ‘compile’ and return its code before the requiring script can continue. So if you were to make all your game systems ModuleScripts instead, your game wouldn’t run when various systems interact with other systems. This way each system is able to offer a ton of useful methods and events that other systems can tap into without being stopped by child/parent hierarchy.