Business Central AL Design Patterns: Strategy, Factory & SingleInstance
As a Business Central extension grows, the difficult part is no longer writing an AL procedure. The difficult part is deciding where that procedure belongs, how implementations can change, and how to avoid creating a codeunit that knows everything about the application.
1. What is a design pattern in AL?
A design pattern is a reusable approach to structuring code around a common problem. In Business Central, patterns are usually implemented with familiar AL building blocks such as codeunits, interfaces, enums, events, records and background processing.
Microsoft documents interfaces as contracts that allow different implementations while reducing dependency on implementation details. Events similarly provide extension points that separate custom behavior from application logic. These capabilities make several classic design ideas practical in AL.
2. Strategy Pattern: choose behavior without changing the caller
The Strategy pattern is useful when the same business operation can be performed in multiple ways. Instead of writing a large case statement containing every implementation, define a common interface and let separate codeunits implement it.
interface "Shipping Strategy"
{
procedure CalculateCost(OrderNo: Code[20]): Decimal;
}
codeunit "Standard Shipping" implements "Shipping Strategy"
{
procedure CalculateCost(OrderNo: Code[20]): Decimal
begin
exit(100);
end;
}
codeunit "Express Shipping" implements "Shipping Strategy"
{
procedure CalculateCost(OrderNo: Code[20]): Decimal
begin
exit(250);
end;
}
The consumer can work with the interface rather than knowing the internal calculation of every shipping method.
When Strategy is useful
- Multiple payment or shipping calculation methods.
- Different tax or pricing rules.
- Multiple AI providers.
- Different external integration implementations.
- Customer-specific or country-specific business behavior.
3. Selecting a Strategy in AL
The strategy itself is only half of the design. Something still has to select the correct implementation. An enum or setup record can hold the business choice, while a small resolver codeunit maps that choice to an interface implementation.
enum 50100 "Shipping Method"
{
Extensible = true;
value(0; Standard) { Caption = 'Standard'; }
value(1; Express) { Caption = 'Express'; }
}
codeunit "Shipping Strategy Resolver"
{
procedure GetStrategy(Method: Enum "Shipping Method"): Interface "Shipping Strategy"
begin
case Method of
Method::Standard:
exit(StandardStrategy);
Method::Express:
exit(ExpressStrategy);
end;
end;
var
StandardStrategy: Codeunit "Standard Shipping";
ExpressStrategy: Codeunit "Express Shipping";
}
The exact implementation may differ depending on the solution. The architectural goal is to keep selection logic in one place and keep the business process independent of concrete implementations.
4. Factory-style pattern: centralize object creation and selection
A Factory-style pattern is useful when creating or selecting an implementation requires more than one simple assignment. The factory becomes the place where the application decides which service should be used.
codeunit "AI Provider Factory"
{
procedure GetProvider(ProviderType: Enum "AI Provider Type"): Interface "AI Provider"
begin
case ProviderType of
ProviderType::ProviderA:
exit(ProviderA);
ProviderType::ProviderB:
exit(ProviderB);
end;
end;
var
ProviderA: Codeunit "AI Provider A";
ProviderB: Codeunit "AI Provider B";
}
This can be valuable in an AI + Business Central project because the application service should not need to know authentication, endpoint or provider-specific implementation details.
5. Strategy vs Factory
| Pattern | Main purpose | Typical AL use |
|---|---|---|
| Strategy | Swap business behavior | Pricing, shipping, tax, AI provider |
| Factory-style resolver | Centralize implementation selection | Choose service/provider from setup or enum |
They can be used together. The factory selects a strategy; the strategy performs the operation.
6. SingleInstance codeunits: useful, but easy to misuse
Business Central supports the SingleInstance property on codeunits. When it is true, codeunit variables using that codeunit share the same instance and internal variables for the same client session. Microsoft notes that the instance remains instantiated until the company is closed.
codeunit 50101 "Session Context"
{
SingleInstance = true;
var
CurrentRequestId: Guid;
procedure SetRequestId(NewRequestId: Guid)
begin
CurrentRequestId := NewRequestId;
end;
procedure GetRequestId(): Guid
begin
exit(CurrentRequestId);
end;
}
This can be useful for session-level state that genuinely belongs to the same logical client session. It should not be treated as a general-purpose database or permanent cache.
Good use cases
- Small session-scoped context.
- State shared between codeunit calls during the same client session.
- Specific subscriber/event scenarios where a single instance is intentionally part of the design.
Common mistakes
- Using SingleInstance to store persistent business data.
- Assuming the state is shared across all users or sessions.
- Keeping large amounts of data in internal variables.
- Relying on state when a table or explicit parameter would make the design clearer.
Microsoft's SingleInstance documentation states that the shared instance behavior applies to codeunit variables on the same client and that the instance remains until the company is closed. Always design around the actual lifetime you need.
7. Event-driven pattern: extend without tightly coupling code
Events are another important AL design pattern. A publisher raises an event at a meaningful extension point, and subscribers add behavior without changing the publisher's main implementation.
[IntegrationEvent(false, false)]
local procedure OnAfterCustomerAnalysis(CustomerNo: Code[20])
begin
end;
[EventSubscriber(ObjectType::Codeunit,
Codeunit::"Customer Analysis",
'OnAfterCustomerAnalysis',
'', false, false)]
local procedure HandleAnalysis(CustomerNo: Code[20])
begin
// Extension-specific processing
end;
Microsoft describes events as a way to separate custom functionality from application business logic. That separation can reduce the cost of code modifications and upgrades.
8. Combining patterns in a real integration
Consider a Business Central order synchronization feature that supports two external systems. A clean design can combine the patterns:
Sales Order
│
▼
Sync Application Service
│
▼
Integration Factory
│
┌──┴─────────┐
▼ ▼
Shopify ERP API
Strategy Strategy
│ │
▼ ▼
HTTP/JSON HTTP/JSON
│
▼
Response Validation
│
▼
Business Event
The application service coordinates the workflow. The factory selects the implementation. Each strategy handles one integration. Events provide optional extension points for logging, notifications or follow-up processing.
9. Don't turn patterns into over-engineering
A common mistake among developers learning architecture is adding interfaces, factories and abstractions everywhere. If a process has exactly one stable implementation and no realistic variation, a simple codeunit may be clearer.
Use a pattern when there is a real design pressure:
- There are multiple implementations.
- A requirement is expected to vary by setup or business context.
- External providers may change.
- Testing benefits from replacing an implementation.
- Different extensions need controlled extension points.
10. Performance considerations
Good architecture does not automatically make code fast. The implementation still needs efficient data access, controlled database work and appropriate asynchronous processing.
Microsoft's performance guidance recommends patterns such as set-based operations, partial records, asynchronous execution and limiting unnecessary work. It also notes that event subscriptions and subscriber code can affect performance, so subscribers should remain focused and appropriately sized.
Do not put expensive database processing into a frequently executed page trigger merely because an event or pattern makes it convenient. Move work to a suitable service or background process when the user does not need the result synchronously.
11. Testing pattern-based AL code
Pattern-based code is especially valuable when the boundaries are testable. A test can exercise the business process with a selected strategy and separately test each implementation.
Test: Standard shipping
↓
Resolver → Standard Strategy
↓
Expected cost
Test: Express shipping
↓
Resolver → Express Strategy
↓
Expected cost
Test: Invalid configuration
↓
Resolver
↓
Expected validation/error
This reduces the need for every test to exercise the complete external integration stack. External calls should be isolated behind suitable boundaries so business rules can be tested independently.
12. AI + Business Central architecture example
For an AI-enabled extension, the same principles can keep the AI layer controlled:
Business Central Page
│
▼
AI Application Service
│
▼
AI Provider Interface
│
┌─────┴──────┐
▼ ▼
Provider A Provider B
│
▼
Response Validation
│
▼
Human Approval / Business Rule
│
▼
Controlled BC Action
The AI response should not automatically become a trusted database command. The application should validate the response, enforce permissions and business rules, and require human review where the process calls for it.
13. Interview questions you should prepare
- What is the Strategy pattern and how would you implement it in AL?
- Why would you use an interface instead of a large CASE statement?
- What is a Factory-style resolver?
- Can Strategy and Factory be used together?
- What does
SingleInstance = truedo? - Is a SingleInstance codeunit shared between all users?
- When should you avoid SingleInstance?
- How do events reduce coupling in Business Central?
- How would you design an AL integration that supports multiple providers?
- How would you test different Strategy implementations?
- When is an abstraction over-engineering?
- How would you combine interfaces, events and background processing in a production extension?
14. Interview-ready answer
Conclusion
Business Central provides enough language and extensibility features to build clean application architectures without forcing a traditional object-oriented framework onto AL. Interfaces, codeunits, enums, events and background processing can be combined into practical patterns that solve real Business Central problems.
The strongest AL architecture is usually the one that makes responsibilities obvious, dependencies controlled and future changes manageable.