The pitfalls of centralized frameworks and SSA
I wanted to make this my first blog post because I feel like I have this conversation every day! Centralized frameworks have been such a big thing for Roblox development for such a long time, but many people don’t understand what problems they are trying to solve, instead opting to use them because they seem more “advanced”.
First, I want to define what I’m talking about here. I am talking specifically about a framework that runs everything on your game using a single-script architecture (SSA for short). These frameworks often inject modules in runtime, allowing you to do something like Framework.MyModule to access controllers, services, and more. Examples of this include Knit (by sleitnick) and Yucon (by me). I am not talking about utility collections, such as Nevermore or RbxUtil.
Where do centralized frameworks come from?
Originally, frameworks were originally designed to solve architecture problems on Roblox. Prior to FilteringEnabled, there was not proper server/client separation, and remotes were considered “new” around the invention of “Team Crazy Game Framework” (also developed by sleitnick, prior to Knit). Frameworks were made to help separate the client and server, providing tools for networking, organization, and more.
In sleitnick’s blog post about this, he explains it this way:
It did exactly what I had hoped it would do. I didn’t have to create RemoteEvents or RemoteFunctions by hand! That ridiculous concept was swept away.
This solved a very real problem back then… but in the modern era, it’s not solving anything.’
Why are centralized frameworks bad?
If you want the simple answer, it’s because frameworks sacrifice intellisense, typechecking, and sometimes proper organization. In some frameworks, you also get some memory buildup/leaks, which are not nice. Most importantly, though, you make onboarding more difficult, which can increase hiring costs and sometimes shrink your talent pool.
Unfortunately, Luau’s typechecking and intellisense doesn’t support the way frameworks are structured. This means that if you inject modules into other modules in runtime, you’re not getting any autofill for what you’re doing. This creates a lot of friction when calling functions, setting values, or anything else where autofill would be helpful. You could manually try to typecheck everything into the framework… but at that point you’re just patching up what is already bad design.
Frameworks often times have a limited way of organizing your codebase, preventing you from sorting things into folders. Considering that this isn’t the case for every framework, I won’t make it a dealbreaker. Same with the memory issues.
What I will get more into, though, is the onboarding. From a producer’s perspective, when I hire somebody, I want it to be as easy as possible. The way you decrease friction in the onboarding process is by simplifying structure and making code easy to understand at a glance. Frameworks are notoriously bad for onboarding, since you are basically changing the way the developer has to code from the ground-up. This issue is also compounded from the lack of intellisense, meaning that they have to lean more into reading everything to understand what is actually happening.
The counter-argument I hear for this is that documentation solves everything, but unfortunately it just does not. Documentation is vital for helping the team be on the same page with libraries and systems, yes, but it does not decrease onboarding time. Code should be designed to be as readable and understandable as you can make it with the least amount of information as possible. This means just… designing good code to begin with.
What are alternatives?
If you want to mimic controllers, services, or whatever else… just use modules. Module scripts are amazing, because they can serve any function and be placed anywhere without breaking. If you wanted to mimic a similar structure to Knit or Yucon, for example, you could just make folders of modules named a similar way.
When you avoid using a central framework, you also can structure your code better. As I documented in the Oxomo Coding Conventions, this is how I structure my game:
.
└── Project/
├── ReplicatedFirst/
│ ├── Scripts
│ └── Modules
├── ServerScriptService/
│ ├── Scripts
│ ├── Modules
│ └── Data
├── ReplicatedStorage/
│ ├── Modules/
│ │ └── Packages
│ ├── Data
│ ├── Assets
│ └── Remotes
└── ServerStorage/
└── Assets
If you’re concered about load order… then just use a module loader to initialize stuff. Just don’t inject modules, since then you’re creating the same problem again. If you want, I created a module for this. That being said, this can often times be avoided by just writing code without race conditions. I usually do not use an intializer.
And, if you’re concerned about networking… just use remote events. If you really want to do something “more advanced”, then you could just use a networking library. I usually just choose to do remote events though.
Is SSA without a framework still bad?
You can definitely do a single-script architecture without a framework and make it work out… but what problem are you really solving with it? It doesn’t hurt, but it also doesn’t help. I want to also add that SSA is more vulnerable to memory overhead issues, just like centralized frameworks.
I opt to do multiple scripts on both the client and server. This allows me to contain functionality in certain places, which makes it easier for other developers on the team to navigate through. One small benefit of this too is that it allows error tracebacks to be shorter, which is always nice.
Conclusion
Central frameworks are just bad design in modern Roblox. To loop back to the same fella, sleinick said it best in Knit’s archival message:
I do not get sentimental about software. Code is written to solve a problem. When the problems change (or the tools to help solve those problems change), then the code typically needs to change too. Sometimes this means abandoning old solutions in favor of newer ones.
On a personal note, I’m not very upset about this. I too have written a framework in the past, but like everybody else, it’s time to move on.
Thanks for reading my thoughts on this, and I hope you learned something new.