Rogen IconRogen
Architectures

Feature Folders

One folder per feature, with its server, client and shared code inside.

The baseline, and the reason Rogen exists. Also known as package by feature, or vertical slices.

The Rule

Everything one feature needs goes in one folder, and nothing else does. Inside the feature folder, a routing folder per side says where each piece runs:

  • Server/ for code only the server runs,
  • Client/ for code only clients run,
  • Shared/ for code both sides require: types, config, remote definitions.

Features stay independent of each other. Code that several features need becomes a feature of its own, named after what it does.

Where It Comes From

  • Package by feature. John O'Hanley's Package by feature, not layer argues that grouping by feature gives "packages with high cohesion and high modularity, and with minimal coupling between packages". His test for modularity is deletion: "an item has maximum modularity only if it can be deleted in a single operation".
  • Vertical slices. Jimmy Bogard's vertical slice architecture says the same for backends: "Minimize coupling between slices, and maximize coupling in a slice". A feature folder is a vertical slice.
  • Domain-driven design. Eric Evans' DDD Reference asks for modules that "tell the story of the system", named in the language of the domain. On Roblox, that means Trading/ and Quests/, not Services/.
  • Game engines. Godot's project organization advice is to "group assets as close to scenes as possible", because it keeps a growing project maintainable.
  • Roblox. Nevermore puts Client/, Server/ and Shared/ inside every package, the same shape as a Rogen feature folder.

On Disk

A small game with three features:

File System
src
Door
Client
DoorClient.luau
Server
DoorServer.luau
Shared
DoorConfig.luau
Inventory
Client
InventoryController.luau
Server
InventoryService.luau
Shared
InventoryTypes.luau
Shop
Client
(Ui)
ShopItemCard.luau
ShopWindow.luau
ShopState.luau
Server
ShopService.luau
Shared
ShopCatalog.luau

Inventory is the plain case: a service on the server, a controller on the client and the types both sides share. The other two show that common game patterns fit inside a feature folder.

A Component: Door

The component pattern exists to "allow a single entity to span multiple domains without coupling the domains to each other". On Roblox, a component is usually bound to every instance with a tag, and tags replicate from the server to the client, so both sides find the same doors.

  • DoorServer owns the state: it checks who may open a door, and opens it.
  • DoorClient animates the door and plays its sound.
  • DoorConfig holds the tag name and timings both sides read.

DoorServer ends in Server, which is also a route key. The Server/ folder around it is the outer route, so it governs and the suffix is ignored: the module keeps its full name. See The Governing Route.

UI With a View Model: Shop

In MVVM, a view model holds the state of a view, and the view only draws it. The split maps onto the sides of a game:

  • Model: ShopCatalog (the items and prices, shared) and ShopService (the purchase, which only the server may decide).
  • View model: ShopState, the client-side state of the window: whether it's open, which item is selected.
  • View: ShopWindow and ShopItemCard, written with a UI library such as Fusion or react-lua.

(Ui) is an invisible folder: it groups the views on disk and disappears from Studio.

The Config

The routes rogen init writes are all this layout needs. The invisible folder needs no config.

default.rogen.json
{
	"$schema": "https://ldgerrits.github.io/rogen/schema/2/rogen.json",
	"rootDirs": ["src"],
	"routes": {
		"Server": "ServerScriptService",
		"Client": "StarterPlayer/StarterPlayerScripts",
		"Shared": "ReplicatedStorage/Shared",
		"*": "ReplicatedStorage/Shared"
	}
}

In Studio

Each feature folder appears once in every service it has code in. The routing folders and (Ui) are gone.

Roblox Studio
ReplicatedStorage
Shared
Door
DoorConfig
Inventory
InventoryTypes
Shop
ShopCatalog
ServerScriptService
Door
DoorServer
Inventory
InventoryService
Shop
ShopService
StarterPlayer
StarterPlayerScripts
Door
DoorClient
Inventory
InventoryController
Shop
ShopItemCard
ShopState
ShopWindow

Why It Fits a Game

  • A gameplay feature spans every side. Input on the client, validation on the server, types in between. A feature folder keeps all three in one place, so a change to the shop is a change to Shop/.
  • Removing a feature is one delete. Shop/ goes, and so does every script it placed in Studio. That's O'Hanley's test for modularity.
  • Server code stays on the server. Server/ routes to ServerScriptService, which isn't replicated, so exploiters can't read it. Keeping the split inside the feature makes it visible in every folder, not just at the root.
  • The tree tells you what the game is. The top level reads Door, Inventory, Shop, not server, client, shared.

Trade-offs

  • Code used everywhere needs a home. A Signal class or a remote wrapper belongs to no single feature. Give it a feature folder of its own, named after what it does. When there are many of these, the layered layout gives them a place.
  • Rogen doesn't stop a feature from reaching into another. It doesn't read requires. Because the feature is in every path, a linter can: see Enforcing the Rules.
  • Paths get one level deeper. Requires read ReplicatedStorage.Shared.Shop.ShopCatalog instead of ReplicatedStorage.Shared.ShopCatalog.
  • Big features need sub-folders. A feature can hold folders of its own, and routing folders can sit at any depth inside them.

Routing folders are the recommended way to route here. Suffixes such as Door-server.luau work too, but read confusingly next to Rojo's .server.luau. See Suffixes.

On this page