Interfaces in AL and why that matters
While NAV TechDays 2019 was an amazing conference with a lot of highlights and interesting news, one to me clearly stood out: The announcement that Business Central will get interfaces as part of the AL programming language. I was very happy about that, as my immediate reaction shows:
INTERFACES! #msdyn365bc will get INTERFACES. Finally a language construct that allows thinking about an extensible application architecture š š (sorry to event fans, but events are events, not a reasonable extensibility mechanism) pic.twitter.com/igp1qNrEVt
— Tobias Fenster (@tobiasfenster) November 21, 2019
What does it look like?
While this was in the āFrom the labā part of the keynote delivered by Vincent Nicolas, which always comes with the disclaimer that the content might actually not make it into the product, it now also is in the 2020 Release Wave 1 release notes, so it chances are good that we actually will see this appear in the product. And indeed, if you have access to the insider Docker images of Business Central, you can already give it a try, e.g. like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
interface ISportsEvaluation
{
procedure GetEvaluation(): Text;
}
codeunit 50100 Basketball implements ISportsEvaluation
{
procedure GetEvaluation(): Text;
begin
exit('Basketball is cool');
end;
}
codeunit 50101 Tennis implements ISportsEvaluation
{
procedure GetEvaluation(): Text;
begin
exit('Tennis is fun');
end;
}
codeunit 50102 Soccer implements ISportsEvaluation
{
procedure GetEvaluation(): Text;
begin
exit('Soccer sucks');
end;
}
codeunit 50103 Evaluation
{
local procedure Evaluate(var se: interface ISportsEvaluation)
begin
se.GetEvaluation();
end;
}
As you can see, in the beginning a new interface ISportsEvaluation
is defined and it has a single procedure GetEvaluation
(lines 1-4). Note that only the name, parameters, return type and visibility of the procedure is defined, but not the actual code. Then three codeunits are defined which implement ISportsEvaluation
(lines 6-28), which means they need to add actual code for the GetEvaluation
procedure. And then in the end you see an additional codeunit which defines a procedure that gets a ISportsEvaluation
as parameter (lines 30-36). Using that, it can contain code that just calls the GetEvaluation
method and will get the right implementation, depending on the codeunit that was passed in. The interface construct in AL also supports implementing multiple interface, e.g. like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
interface ISportsEvaluation
{
procedure GetEvaluation(): Text;
}
interface IBallColorIdentifier
{
procedure GetBallColor(): Text;
}
codeunit 50100 Basketball implements ISportsEvaluation, IBallColorIdentifier
{
procedure GetEvaluation(): Text;
begin
exit('Basketball is cool');
end;
procedure GetBallColor(): Text;
begin
exit('orange/brown');
end;
}
codeunit 50101 Tennis implements ISportsEvaluation, IBallColorIdentifier
{
procedure GetEvaluation(): Text;
begin
exit('Tennis is fun');
end;
procedure GetBallColor(): Text;
begin
exit('yellow');
end;
}
codeunit 50102 Soccer implements ISportsEvaluation, IBallColorIdentifier
{
procedure GetEvaluation(): Text;
begin
exit('Soccer sucks');
end;
procedure GetBallColor(): Text;
begin
exit('black/white');
end;
}
codeunit 50103 Evaluation
{
local procedure Evaluate(var se: interface ISportsEvaluation)
begin
se.GetEvaluation();
end;
local procedure GetBallColor(var bc: interface IBallColorIdentifier)
begin
bc.GetBallColor();
end;
}
Now there is a new interface IBallColorIdentifier
(lines 6-9) which defines the procedure GetBallColor
and is implemented by all ISportsEvaluation
codeunits as well.
Extendable enumerations, powered by interfaces
Pretty nice, but why does it actually matter? Microsoft has already provided one scenario and that is an extendable enumeration. Letās assume that we have an enum for ball colors:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum 50100 BallColors
{
value(0; Tennis)
{
}
value(1; Basketball)
{
}
value(2; Soccer)
{
}
}
Usually this results in case statements like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local procedure IdentifyBallColor(var ballColor: enum BallColors): Text
var
colorText: Text;
begin
case ballColor of
BallColors::Tennis:
colorText := 'yellow';
BallColors::Basketball:
colorText := 'orange/brown';
BallColors::Soccer:
colorText := 'black/white';
else
colorText := 'unknown';
end;
exit(colorText);
end;
This is a) ugly and b) very problematic if you want to add additional ball colors in another extension because the IdentifyBallColor
procedure will always return āunknownā for anything other than Tennis, Basketball and Soccer. The idea introduced at TechDays now is to make an enum extensible and defining which interface the enum elements need to implement. So to make use of our IBallColorIdentifier
interface and the codeunits we already have in place, we would do something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum 50100 BallColors implements IBallColorIdentifier
{
Extensible = true;
value(0; Tennis)
{
Implementation = IBallColorIdentifier = Tennis;
}
value(1; Basketball)
{
Implementation = IBallColorIdentifier = Basketball;
}
value(2; Soccer)
{
Implementation = IBallColorIdentifier = Soccer;
}
}
You can see the definition of the implementing codeunits for every enum element in lines 6, 11 and 16. As a result we can rewrite the IdentifyBallColor
procedure in a much better way:
1
2
3
4
5
6
7
8
local procedure IdentifyBallColor(var ballColor: enum BallColors): Text
var
colorText: Text;
identifier: interface IBallColorIdentifier;
begin
identifier := ballColor;
exit(identifier.GetBallColor());
end;
In my opinion it would be nice if we wouldnāt need to do the cast-like thing in line 6, but still it is a dramatic improvement. It follows the strategy design pattern, one of the patterns introduced in the ābibleā of design patterns, a book simply called āDesign Patternsā from the early nineties authored by the āGang of Fourā1. I am old enough to have read this book while it was quite new, but I think it still is an extremely valuable read for any developer and very good to see BC / AL taking up more of the principles from that book. But how does this help with extending the enum? The following sample shows you how you can just add a new enum element in a different extension, including the implementation for the interface and our fantastic new IdentifyBallColor
procedure will continue to work:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enumextension 50100 FootballColor extends BallColors
{
value(3; Football)
{
Implementation = IBallColorIdentifier = Football;
}
}
codeunit 50100 Football implements IBallColorIdentifier
{
procedure GetBallColorIdentifier(): Text;
begin
exit('brown');
end;
}
Also note how this allows to extend the enum but not actually modify the behavior (or strategy) of the existing enum elements. That also is a design principle which helps to build trust but I am curious to see whether Microsoft will provide additional mechanisms to overcome that limitation (if you consider it a limitation) as well.
Is that all?
Weāve heard Microsoft talk a lot about ācomponentizationā of the base app. I think that makes a ton sense and if you look outside of the BC universe, it is very clear that old-school monoliths are not the best way to go2. Principles like separation of concerns can be followed a lot easier if you have a solid component model. However in order to componentize your code base, you need to have a good, stable and reliable mechanism to define and use a component. While there are different ways to tackle that problem, it is one of the basic principles that a component is something with a clearly defined interface, i.e. the functions or procedures of a component are defined including their input parameters and return value. If you look at the way AL interfaces are constructed, that works fine. E.g. a component for using a number sequence could have an interface INumberSequence
like this (you can see where this comes from, just a bit simplified)
1
2
3
4
5
interface INumberSequence
{
procedure Current(Name: String): BigInteger;
procedure Next(Name: String): BigInteger;
}
This actually is way more important to me than the extendable enums! While those are sure interesting and solve real life problems, the introduction of a real componentization model has a lot broader consequences because as already mentioned now you can really start to componentize and think about application architecture and then the next step is the ability to replace components, which also is a very important point when discussing the future of Business Central. While the existing extension model solves a lot of problems, it clearly has limitations in other areas, like the inability to replace code. I know there is the āhandledā pattern, but it relies on the existence of the right events and more importantly it relies on developers adhering to the conventions of that pattern with no real way to enforce it. I donāt like that approach even if you have all developers inside of the same company, but when you start to think of 3rd-party extensions running side by side in one tenant, I really doubt that it will work reliably. Fortunately with interfaces we now have the beginnings of a better way to handle that.
The substitution of components can happen either at design time or at runtime. For design time in the Business Central world that could mean that you remove a codeunit which implements an interface and provide another codeunit implementing the same interface. While that is interesting and could be helpful when you think about redesigning or changing your solution, it still wonāt solve the problem to replace base code. However it could also happen dynamically during runtime. Currently I havenāt seen anything how that might work, but if I let my imagination run wild, I could see something in the app.json file which defines the interfaces an extension wants to implement. Then maybe there could also be something to define whether implementation is mandatory and needs to replace other implementations or is optional and the extension would also work with other implementations. That could then also be considered by the Business Central server as a factor whether an extension can be installed or not. Again, just me imagining things, but lets assume we have a our INumberSequence
interface, then an extension could maybe do something like this to replace the current implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"id": "3c43035b-3f7e-4b42-b293-c8cf80d1cf98",
"name": "MyExtension",
...
"dependencies": [
...
],
"interfaces": [
{
"name": "INumberSequence",
"implementationMode": "mandatory"
}
]
}
Now if some code wants to get a number sequence, it would just ask the platform for the current implementation of the INumberSequence
interface and use that. If the default is still in place, that is used and if some extension has replaced it, the replacement is used. This would also require some kind of dependency injection mechanism, but that also is something that becomes possible now that we have interfaces! Maybe it could be something like this:
1
2
3
4
5
6
7
8
9
local procedure CreateInvoice()
var
numberSequence: inject interface INumberSequence;
nextNumber: BigInteger;
begin
...
nextNumber := numberSequence.Next('mySequence');
...
end;
Overall, I am still very happy to once again see Microsoft invest into the future of Business Central, in this case by making AL a stronger language that allows us to build better software. I canāt wait to see how far and in which direction they take interfaces and the various scenarios that are now enabled.
Webmentions:
-
Very nice write-up from @tobiasfenster about upcoming āinterfacesā in #msdyn365bc AL!
-
-
When I heard about Interfaces first I felt amazed. Very valuable post from the Docker boss. #IWishItWasApril
-
-
Read what @tobiasfenster wrote about Interfaces in AL for #D365BC - very good examples! tobiasfenster.io/interfaces-in-ā¦
-
-
Weāre waiting always more OO features for AL. #MsDyn365BC
-
-
-
-
Some of sports evaluations in samples are arguable @tobiasfenster š, thanks for great post!
-
-
The upcoming Dynamics 365 Business Central 2020 Wave 1 release (version 16) introduces the concept of Interfaces in AL.
An interface is used when you want to decide which capabilities need to be available for an object, while allowing actual implementations to differ, as long as they comply with the defined interface. This allows for writing code that reduces the dependency on implementation details, makes it easier to reuse code, and supports a polymorphing way of calling object methods, which again can be used for substituting business logic.
With the new AL language version, you can use the new interface object to declare an interface name along with its methods, and apply the implements keyword along with the interface names on objects that implement the interface methods. The interface object itself does not contain any code, only signatures, and cannot itself be called from code, but must be implemented by other objects. The compiler checks to ensure implementations adhere to assigned interfaces.
A great post that describes interfaces in AL was written by Tobias Fenster some weeks ago and you can find it here.
As Iāve described in my previous post, Microsoft is using the interface object in the new version 16 codebase on different places and the first place that comes to my eyes when building my apps for version 16 was on the sales price calculation module.
Dynamics 365 Business Central 2020 Wave 1 (or version 16) has a totally new way of handling sales prices based on AL Interfaces. If you check for example the OnValidate event of the Quantity field of the Sales Line table, now you can see that the new Interface āPrice Calculationā object is used:
In this OnValidate trigger, to retrieve the price for the Sales Line record the AL code calls a GetPriceCalculationHandler procedure as follows:
Inside this procedure, thereās a call to a GetHandler method (defined in the Price Calculation Mgt. codeunit) by passing the interface:
<p>The GetHandler method retrieves from the Price Calculation Setup table the value of the Implementation field and then call the Init method defined in the interface itself:</p>
<p>This field is an enum field type called Price Calculation Handler:</p>
<p>and the enum is defined as follows:</p>
<p>As you can see, you can extend it with your own implementationās values.</p>
The PriceCalculation interface is defined as follows (contract):
<p>What happens in the new 2020 Wave 1 codebase? A marvellous thingā¦ the new price calculation implementation based on interfaces permits you to define your custom price logic by implementing the PriceCalculation interface on a separate codeunit. If you want to add your custom price management logic to Dynamics 365 Business Central, now you donāt have to modify code or interact with events raised by the standard, but simply add your new interface implementation (codeunit) and add it on the Price Calculation Setup table.</p>
The standard codebase has two implementations for the PriceCalculation interface: Price Calculation ā V15 (logic until Dynamics 365 Business Central version 15) and Price Calculation ā V16 (the new logic for Dynamics 365 Business Central version 16). This is how Price Calculation ā V16 is implemented:
and this is the the implementation for Price Calculation ā V15 (as you can see, itās declared as ObsoleteState = Pending):
To create your custom price calculation in your extension, just add a new value in the Implementation field in the Price Calculation Setup table with an enumextension object:
and then create a new codeunit inside your extension by implementing the Price Calculation interface and add your custom logic in the ApplyDiscount and ApplyPrice methods:
Very clean and elegantā¦ š
Share this:
- Click to share on Twitter (Opens in new window)
- Click to share on LinkedIn (Opens in new window)
- Click to share on Facebook (Opens in new window)
- Click to email this to a friend (Opens in new window)
- Click to print (Opens in new window)
- Click to share on Tumblr (Opens in new window)
- Click to share on Pinterest (Opens in new window)
- Click to share on Pocket (Opens in new window)
Like this:
Like Loadingā¦
<h3><em>Related</em></h3>
-
If you want to see the part where I explained it (or at least tried to) during the @areopanl webinar - here you go: youtube.com/watch?v=VDhgV6ā¦
Also - donāt forget @tobiasfenster ās blog: tobiasfenster.io/interfaces-in-ā¦
-
-
-
-
-
-
-