Understanding Interfaces in AL

Interfaces were introduced to Microsoft Dynamics 365 Business Central in version 2020 Release Wave 1 (BC 16). Their arrival marked a significant step toward improving extensibility, modular design, and decoupled architecture within AL development. Before interfaces existed, implementing flexible, interchangeable logic was difficult. Developers often relied on:

  • large conditional statements (IF…ELSE or CASE blocks),
  • events with cumbersome subscriber logic,
  • SingleInstance codeunits acting as service containers,
  • or inheritance-like patterns (which AL does not support).

These approaches worked, but they lacked true polymorphism and made code harder to maintain, test, and extend—especially in multi-extension environments.

Interfaces solved these issues by enabling:

  • polymorphism (different objects sharing the same contract),
  • clean separation of concerns,
  • loose coupling,
  • easy extensibility (especially for ISVs),
  • clean testing via mock implementations.

Understanding interfaces is essential for any modern BC developer. Let’s explore them using the simplest possible example.


Interface Example: Vehicle → Car

To help someone encountering interfaces for the first time, we’ll start with the smallest example:

  • one interface: IVehicle
  • one implementation: Car
  • a simple codeunit calling the interface in OnRun

Because there is only one implementation, Business Central can automatically resolve it.


Step 1: Creating the Interface

interface IVehicle
{
    procedure Drive();
}

This defines a contract:
Every vehicle must be able to drive.


Step 2: Implementing the Interface (Car)

codeunit 90001 Car implements IVehicle
{
    procedure Drive()
    begin
        Message('The car is driving.');
    end;
}

This is a concrete implementation of the interface.


Step 3: Using the Interface in a Codeunit

codeunit 90002 VehicleDemo
{
    trigger OnRun()
    var
        Vehicle: IVehicle;
    begin
        Vehicle.Drive();
    end;
}

Why does this work?

Because there is only one implementation of IVehicle, Business Central automatically injects Car behind the scenes.
This is the simplest and most beginner-friendly demonstration of interfaces in BC.


Adding a Second Implementation: Bike

Now we introduce a second implementation to show what happens and how to solve it.


Step 4: Creating the Bike Implementation

codeunit 90003 Bike implements IVehicle
{
    procedure Drive()
    begin
        Message('The bike is being pedaled.');
    end;
}

Now we have:

  • Car implements IVehicle
  • Bike implements IVehicle

What Happens Now? Compilation Error

If we run our original codeunit:

Vehicle.Drive();

The system no longer knows which implementation to pick.
Multiple implementations exist, and AL cannot choose automatically.

You will get a compilation error similar to:

Multiple implementations found for interface IVehicle. Specify one explicitly.

This is expected behavior.


How to Solve the Multiple-Implementations Problem

There are three common solutions.


Solution 1 — Explicit Assignment (Basic Approach)

Simply tell AL which implementation to use:

Vehicle := Codeunit Car;
Vehicle.Drive();

or:

Vehicle := Codeunit Bike;
Vehicle.Drive();

This works, but it couples the code to a specific implementation.
It’s a basic solution—for learning, not for production.


Solution 2 — Using Enum-Based Implementations (Best Practice)

This is the recommended approach and the one Microsoft uses internally for:

  • email providers,
  • address providers,
  • payment services,
  • API handlers,
  • notifications, etc.

Step 1: Create Enum Implementing the Interface

enum 90000 VehicleType implements IVehicle
{
    Extensible = true;

    value(0; Car)
    {
        Implementation = IVehicle = Car;
    }

    value(1; Bike)
    {
        Implementation = IVehicle = Bike;
    }
}

We can call it “Eum-based Polymorphizm”, this is not an official name but it describe whole meaning.

Step 2: Use It in Code

Vehicle := VehicleType::Bike;
Vehicle.Drive();

Business Central now resolves the correct implementation based on the enum value.

This is the cleanest and most scalable solution.


Solution 3 — Service Provider Pattern (Advanced)

You can store which implementation is active in:

  • setup tables,
  • SingleInstance codeunits,
  • or external configuration.

Example:

Vehicle := VehicleProvider.GetCurrentVehicle();

This is not Dependency Injection

Next example demonstrates a clean, native way of implementing Dependency Injection–like behavior in Business Central using interfaces and enum-based implementation binding.


1. Creating the Interface

We start by defining a simple interface that describes what a notification service must be able to do.

interface INotificationService{
    procedure SendNotification(Recipient: Text; Message: Text);
}

This interface defines a contract:

Any notification service must provide a SendNotification method.

It does not specify how the notification is sent—that is the responsibility of each implementation.


2. Implementing the Interface: EmailNotification

Next, we create the first implementation using an AL codeunit.

codeunit 80000 EmailNotification implements INotificationService
{
    procedure SendNotification(Recipient: Text; Message: Text)
    begin
        Message('Email to %1: %2', Recipient, Message);
    end;
}

This codeunit fulfills the interface contract and simulates sending an email.


3. Implementing the Interface: SMSNotification

We now add a second implementation:

codeunit 80001 SMSNotification implements INotificationService
{
    procedure SendNotification(Recipient: Text; Message: Text)
    begin
            Message('SMS to %1: %2', Recipient, Message);
    end;
}

This codeunit represents sending an SMS message.


4. Creating an Enum That Selects the Implementation

This is the key part of the example.

In Business Central, an enum can implement an interface by mapping each enum value to a specific implementation.
This allows AL to automatically select the correct codeunit at runtime.

enum 80000 NotificationMethod implements INotificationService
{
    Extensible = true;

    value(0; Email)
    {
        Implementation = INotificationService = EmailNotification;
    }

    value(1; SMS)
    {
        Implementation = INotificationService = SMSNotification;
    }
}

What this means:

  • NotificationMethod::Email → uses the EmailNotification implementation
  • NotificationMethod::SMS → uses the SMSNotification implementation

This is a native AL mechanism for clean, safe dependency resolution.


5. Consuming the Interface Through the Enum

Now we write a codeunit that uses the enum to determine which implementation should be executed.

codeunit 80010 NotificationServiceRunner
{
    procedure Notify(Method: NotificationMethod; Recipient: Text; TextToSend: Text)
    var
        Notifier: Interface INotificationService;
    begin
        // Resolve implementation from enum
        Notifier := Method;

        // Execute the notification
        Notifier.SendNotification(Recipient, TextToSend);
    end;
}

How this works:

  1. The parameter Method determines the desired notification type.
  2. When we assign Notifier := Method;
    Business Central automatically instantiates the correct implementation.
  3. The codeunit then calls the interface method SendNotification, which is executed by either:
    • EmailNotification, or
    • SMSNotification
      depending on the enum value.

There is no need for IF statements, factories, or explicit selection logic—the enum does it all.


6. Example of Using the Notification Runner

In practice, you might call the runner like this:

var
    Runner: Codeunit NotificationServiceRunner;
begin
    Runner.Notify(NotificationMethod::SMS, '555-123', 'Your package has shipped.');
end;

This would trigger the SMSNotification implementation automatically.


7. Why This Pattern makes sense

Using interfaces with enum-based implementation binding provides:

✔ Clean separation of logic

You code against an interface, never against concrete classes.

✔ Easy extensibility

Partners or other extensions can extend the enum and add new notification methods without modifying your code.

✔ Built-in dependency resolution

The AL runtime resolves implementations automatically.

✔ Testability

Mock implementations can be wired up simply by extending the enum.

✔ A natural way to model business rules

This pattern mirrors real-world choose a method scenarios (email, SMS, push, webhook, etc.).


Conclusion

Interfaces in Business Central brought true polymorphism and modular design to AL development. They:

  • reduce coupling,
  • increase flexibility,
  • make extensions more maintainable,
  • allow multiple interchangeable implementations,
  • and support clean architecture patterns.

Using the Vehicle example is the perfect way for beginners to understand the basics and the NotificationService shows how to use it in practice.

    This foundational pattern is the basis for many advanced Business Central features and real-world extension designs.


    Sources

    Interfaces in AL
    Interface in AL – Business Central

    Leave a Reply

    Your email address will not be published. Required fields are marked *