Dependency injection, polymorphism, and plugin architectures in AL
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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 examples