Organizing Your Business Rule Code
As Business Rules grow beyond a handful of lines, keeping all logic inside
Main becomes difficult to read, test, and maintain. This guide covers a standard pattern: promote runtime objects to class-level fields, then dispatch from Main to dedicated methods for each function type.The Class-Level State Pattern
The
Main method receives si, globals, api, and args as parameters. Any helper method you write needs these same objects, which means either passing them as arguments — producing long, repetitive signatures — or finding another way to share them.The recommended approach is to declare these four objects as class-level fields and assign them at the top of
Main:Now every method in the class can access
si, globals, api, and args directly — no parameter threading required.Dispatching by Function Type
With state promoted to class level,
Main becomes a thin dispatcher. Its only job is to assign the fields and route to the right method based on FunctionType:Each dedicated method focuses on a single responsibility, and because the runtime objects live at the class level, these methods can call shared helpers without any extra plumbing.
Finance Rule Example
A complete Finance Rule with class-level state, dispatcher, dedicated methods, and a shared helper:
Notice how
GetMemberFilter uses api directly from class state. Without the class-level pattern, you would need to pass api into every helper call.Dashboard DataSet Example
Dashboard DataSet rules use
args.FunctionType (a DashboardDataSetFunctionType) to determine what the platform is requesting. The api parameter is typed as Object — it is not used in Dashboard rules the way FinanceRulesApi is used in Finance rules.The
GetSummaryReport method reads args.NameValuePairs directly from class state — no need to thread the args object through the call.Dashboard Extender Example
Dashboard Extender rules handle UI lifecycle events. The
args.FunctionType is a DashboardExtenderFunctionType:Each function type has its own method with a clear name, making it easy to find and modify specific behavior.
When To Extract Further
The class-level pattern works well for a single rule file, but as your codebase grows you may find:
- Methods are reused across multiple rules — The same helper logic appears in several Business Rules.
- A single rule file exceeds a few hundred lines — Scrolling through one large class becomes cumbersome.
- You want unit-testable logic — Business Rule classes cannot be tested outside of OneStream.
When this happens, move shared logic into a Workspace Assembly — a separate class file that multiple Business Rules can reference. This keeps each rule lean and your shared logic in one place.
See the Getting Started with Workspace Assemblies guide for how to set up assemblies and reference them from your rules.
Related Content
- Getting Started with Business Rules — Business Rule types, structure, and the full rule skeleton
- Debugging Business Rules — The StringBuilder debug logging pattern, which builds on the class-level field approach shown here
- Getting Started with Workspace Assemblies — Organize your code into multi-file libraries when you outgrow a single rule file