DYNEXAL GUIDE • AL DESIGN PATTERNS

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.

Core idea: a design pattern is not extra complexity. It is a repeatable way to solve a recurring design problem. Use a pattern when it makes dependencies clearer—not simply because the pattern exists.

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

Interview answer: “I use Strategy when the business operation stays the same but the implementation can vary. The interface defines the contract and each codeunit implements one strategy.”

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

PatternMain purposeTypical AL use
StrategySwap business behaviorPricing, shipping, tax, AI provider
Factory-style resolverCentralize implementation selectionChoose 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

Common mistakes

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:

Practical rule: start with the simplest design that keeps responsibilities clear. Introduce an abstraction when the change in requirements justifies it.

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

  1. What is the Strategy pattern and how would you implement it in AL?
  2. Why would you use an interface instead of a large CASE statement?
  3. What is a Factory-style resolver?
  4. Can Strategy and Factory be used together?
  5. What does SingleInstance = true do?
  6. Is a SingleInstance codeunit shared between all users?
  7. When should you avoid SingleInstance?
  8. How do events reduce coupling in Business Central?
  9. How would you design an AL integration that supports multiple providers?
  10. How would you test different Strategy implementations?
  11. When is an abstraction over-engineering?
  12. How would you combine interfaces, events and background processing in a production extension?

14. Interview-ready answer

Sample answer: “In AL, I use design patterns when they solve a real dependency or variation problem. For example, Strategy with an interface lets me support multiple implementations without changing the business process. A Factory-style resolver can centralize implementation selection. I use SingleInstance codeunits only when session-level shared state is intentional. For extensibility, I use events so additional behavior can subscribe without tightly coupling it to the main process. I also keep the design testable and avoid adding abstractions where a simple codeunit is enough.”

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.

Sources & references

← Back to Dynexal Insights