Defines AL interfaces and interface patterns with implementing codeunits, creates extensible enums to switch providers, and enables dependency injection for unit testing AL code with mocks. Use when building plugin architectures, adding loose coupling, replacing conditional logic with polymorphism, or creating Business Central extensions with swappable implementations.
72
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
is/as operators behave correctly (BC25+)Note: BC16+ for basic interfaces. BC25+ for is/as operators and extensible interfaces.
Output: Interface declaration, implementing codeunits, extensible enum, and usage pattern.
interface "<PREFIX> I<ServiceType> Provider"
{
procedure Execute<Operation>(InputData: <InputType>): <ReturnType>;
procedure Cancel<Operation>(TransactionId: Text; InputData: <InputType>): <ReturnType>;
procedure GetProviderName(): Text;
}Placeholder Reference:
| Placeholder | Description | Example |
|---|---|---|
<ServiceType> | Type of service | Payment, Shipping, Notification |
<Provider1>, <Provider2> | Provider implementations | Stripe, DHL, Email |
<Operation> | Main operation name | Payment, Shipment, Message |
<InputType> | Input parameter type | Decimal, Record "Sales Header" |
<ReturnType> | Return value type | Boolean, Text |
codeunit <ID> "<PREFIX> <Provider1> <ServiceType>" implements "<PREFIX> I<ServiceType> Provider"
{
procedure Execute<Operation>(InputData: <InputType>): <ReturnType>
begin
exit(Call<Provider1>API(InputData));
end;
procedure Cancel<Operation>(TransactionId: Text; InputData: <InputType>): <ReturnType>
begin
exit(Call<Provider1>CancelAPI(TransactionId, InputData));
end;
procedure GetProviderName(): Text
begin
exit('<Provider1>');
end;
local procedure Call<Provider1>API(InputData: <InputType>): <ReturnType>
begin
// Provider-specific logic
end;
local procedure Call<Provider1>CancelAPI(TransactionId: Text; InputData: <InputType>): <ReturnType>
begin
// Provider-specific logic
end;
}Create additional codeunits for each provider (<Provider2>, <Provider3>, etc.) implementing the same interface.
enum <ID> "<PREFIX> <ServiceType> Provider" implements "<PREFIX> I<ServiceType> Provider"
{
Extensible = true;
value(0; <Provider1>)
{
Implementation = "<PREFIX> I<ServiceType> Provider" = "<PREFIX> <Provider1> <ServiceType>";
}
value(1; <Provider2>)
{
Implementation = "<PREFIX> I<ServiceType> Provider" = "<PREFIX> <Provider2> <ServiceType>";
}
}codeunit <ID> "<PREFIX> <ServiceType> Manager"
{
procedure Process<Operation>(var SourceRecord: Record <SourceTable>): Boolean
var
ProviderType: Enum "<PREFIX> <ServiceType> Provider";
IProvider: Interface "<PREFIX> I<ServiceType> Provider";
begin
ProviderType := SourceRecord."<PREFIX> <ServiceType> Provider";
IProvider := ProviderType; // Implicit conversion
if IProvider.Execute<Operation>(SourceRecord.<InputField>) then begin
SourceRecord."<PREFIX> <Operation> Processed" := true;
SourceRecord.Modify();
exit(true);
end;
exit(false);
end;
}// Check if interface supports specific type, then convert
if IProvider is "<PREFIX> IAdvanced<ServiceType>" then begin
IAdvanced := IProvider as "<PREFIX> IAdvanced<ServiceType>";
exit(IAdvanced.Partial<Operation>(TransactionId, InputData, '<Reason>'));
end;
exit(IProvider.Cancel<Operation>(TransactionId, InputData));Multiple interfaces: codeunit <ID> "..." implements "I<ServiceType>", "IAdvanced<ServiceType>", "I<ServiceType>Status"
Pattern: Store interface as codeunit variable, inject via Initialize(Provider) procedure.
codeunit <ID> "<PREFIX> <Entity> Processor"
{
var
ServiceProvider: Interface "<PREFIX> I<ServiceType> Provider";
procedure Initialize(Provider: Interface "<PREFIX> I<ServiceType> Provider")
begin
ServiceProvider := Provider;
end;
procedure Process<Entity>(var SourceRecord: Record <SourceTable>): Boolean
begin
exit(ServiceProvider.Execute<Operation>(SourceRecord.<InputField>));
end;
}Mock for tests: Create codeunit implementing interface with configurable behavior (SetShouldSucceed, GetCallCount).
See references/interface-patterns.md for complete mock implementation example.
| Pattern | Description |
|---|---|
| Provider/Strategy | Multiple implementations for same operation (payment, shipping) |
| Factory | exit(ProviderType) - enum implicitly converts to interface |
| Decorator | Wrap interface to add logging, validation, caching |
Best practices: Small focused interfaces, Extensible = true for partner extensions, use enums for selection.
Limitations: Only procedures (no properties/events/variables), only codeunits implement, cannot serialize to records.
| Issue | Fix |
|---|---|
| "does not implement interface" | Verify all interface methods exist with exact signatures |
| Enum doesn't convert to interface | Check implements clause on enum declaration |
is/as operators not available | Requires BC25+ (2024 Wave 2) |
Feedback loop: Fix signature → Re-compile → Verify method calls work polymorphically.
See references/ folder for:
interface-patterns.md - Additional code examples52f8705
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.