| name | new-component |
| description | Creating new components: Controllers, Providers, Services, Facades, Orchestrators, Validators. Use when creating a new UTP Facade, StatusDatabase provider, controller, service, orchestrator, or choosing which architectural pattern to use. Covers: ModuleFacadeBase boilerplate, GetBooleanResult not GetResult, StatusDatabaseProviderBase, provider SRP, pattern selection decision tree, primary constructors, XML documentation. stages: test-implementation, test-validation |
Creating New Components
When to Use
- Creating a new Facade, Controller, Provider, Service, Orchestrator, or Validator
- Choosing which architectural pattern to use for new code
- Extending an existing component
Pattern Selection Decision Tree
| Need | Pattern |
|---|
| Wrap or simplify a UTP module | Facade inheriting ModuleFacadeBase |
| Orchestrate a game feature or flow | Controller (UTP-based where possible) |
| Read game/bank state (no side effects) | Provider inheriting StatusDatabaseProviderBase |
| Multi-step workflow coordinating several components | Orchestrator |
| Reusable business logic with no external state | Service |
Creating a Facade
Location
Shared/Facades/
Mandatory Boilerplate
Every new facade MUST start with this exact skeleton:
namespace Tests.Shared.Facades;
public class {Name}Facade(IUtpCommandService utpService)
: ModuleFacadeBase(utpService)
{
protected override string ModuleName => "{ModuleName}";
private const string SomeCmd = "CommandName";
public async Task<string> SomeStringMethodAsync()
{
var response = await SendCommandGetResponseAsync(SomeCmd);
return response.GetStringResult();
}
public async Task<bool> SomeBoolMethodAsync()
{
var response = await SendCommandGetResponseAsync(SomeCmd);
return response.GetBooleanResult();
}
public async Task<JsonNode> SomeJsonMethodAsync()
{
var response = await SendCommandGetResponseAsync(SomeCmd);
return response.GetJsonNodeResult();
}
public async Task SomeVoidMethodAsync()
{
await SendCommandAsync(SomeCmd);
}
}
Critical Facade Rules
- NEVER use
GetResult<T>() — it is broken for capitalized bool strings ("True", "False"). Always use the specific typed extension methods: GetBooleanResult(), GetStringResult(), GetJsonNodeResult(), GetStringArrayResult(), GetImageArrayResult(), GetFirstParameterValue(), IsErrorResponse()
- All extension methods are auto-imported via
GlobalUsings — no explicit using needed
- Before generating, read an existing facade (e.g.,
MptFacade.cs, ButtonsFacade.cs) to confirm the pattern
Creating a Controller
Location
Shared/Controllers/ or Shared/Controllers/Egm/ for direct EGM operations
Steps
- Inject dependencies via primary constructor
- Implement business logic using facades/services
- Add XML documentation
- Extend before creating: Check if an existing controller already covers part of the flow
Creating a Provider
Location
Shared/Providers/ or Shared/Providers/StatusDatabase/
Steps
- Inherit from
StatusDatabaseProviderBase, implement IProvider
- Use primary constructor for dependencies
- Use
GetStatusValueAsync<T>() for typed retrieval
- Add XML documentation
Rules
- One module per provider: Each provider maps to exactly one
StatusDatabasePaths nested class
- Naming convention:
{StatusDatabaseNestedClassName}StatusProvider
- Search before creating: Not all game data comes from StatusDatabase — some comes from UTP module responses (e.g., reel data from
MptFacade)
- Provider simplicity: One
GetStatusValueAsync<T>() call per method. No candidate-path arrays or try/catch fallbacks
Creating a Service
Location
Shared/Services/ for reusable, Features/<Feature>/Services/ for feature-specific
Steps
- Keep stateless when possible
- Define interface in
Shared/Interfaces/ if needed
- Use primary constructor
- Add XML documentation
Creating a Test
Location
Features/<Feature>/ — NEVER in Tests/ root
Steps
- Inherit from
EgmTestBase
- Add
using BugSweeper.Tests; (not in GlobalUsings)
- Add XML documentation
- Reuse over duplication: Check if logic belongs in an existing controller/provider/orchestrator
- Extract common preconditions: Repeated preambles belong in controllers or shared helpers