Inverting Dependencies with Interfaces in Business Central Application Language
How to effectively use interfaces in AL and manage your Business Central application source code better.
Although interfaces were introduced in Business Central 2020 release wave 1, it can still be a fairly new concept for Business Central (BC) developers who have not programmed in other language such as C#, TypeScript, or Java. Interfaces are not new in the software engineering world, and it’s never too late to catch up with the rest of the industry on the good practices of writing and maintaining source code, and in particular, programming against an interface, not an implementation. Let’s have a look at an evolution of a simplistic piece of software to demonstrate the benefits of using interfaces.
Disclaimer: the names of companies and providers used in this blog post except Microsoft are fictitious. Any similarity to the names is entirely coincidental.
Setting the stage
To find a theme for our simple solution written in Application Language (AL), we’ll refer to the trendy tool the companies are implementing these days - AI. Let’s imagine, our business leaders asked us to create a Business Central application for helping with some users' questions, and we’re the developers from the past without interfaces in AL. We (at least I) have no idea how to build proper prompts to real AI services, and although it won’t often happen in real life, our manager agreed with my suggestion that calling real services of AI providers is out of scope of our solution. We decided to hardcode the responses from the services. Although everyone feels like this implementation may not bring any value to the business and could be a complete waste of time, the product should be enough to see the power of adding interfaces to your software development tool set. That’s the real purpose of the project - learning something new!
We’ll start with a simple AL application containing a page, an element to enter a question from a user, and an action button to get a response from the fake AI services. The page code below will be enough to get first development results.
And the codeunit provides an answer already, which is a good start.
Our current solution is action should look like in the figure below.
Let’s call our first two fake AI service providers Bingle and Alfasoft. The system logic to call their services is a bit different and can change later, so we’ll put them in dedicated codeunits and will call them from our main Fake AI Service Mgt. codeunit. To specify the provider, we’ll create an extensible Enum object and add a new field with it to the page just above the Question element.
The main codeunit GetAnswer procedure now needs the Provider parameter so that depending on the enum value that we select on the page, we are able to get answers from the service providers.
The dedicated to service provider logic codeunits are listed below.
Notice how we must update the main Fake AI Service Mgt. codeunit every time we need to add a service provider. All the services implementations are explicitly referenced in the area where we defined our variables. This is called a tightly coupled code - changing in one codeunit often must be reflected in the other codeunit as well.
Now imagine that our service providers list keeps growing as well as related features and the main codeunit at some point becomes hard to read. The cases when one service was broken after making changes for another have started to pop up and eventually no one wants to touch that code anymore. The time it takes to ensure that nothing is broken starts to delay delivery of new application features.
To add fuel to the flame, a couple of our customers want to add their custom AI breakthrough solutions to the list as well but it sounds like we have no other choice apart from letting them customize our application for them and include their codeunits into our codebase. We don’t believe it will be a good approach for maintaining and developing our solution further. Time to seriously think of a new way of organizing our code. Luckily for us, Microsoft introduced interfaces to Business Central Application Language, and we’ve decided to explore this new platform feature.
Adding an interface
From the Microsoft Learn article about interfaces in AL we understood that an interface is a syntactical contract which needs to be implemented. Let’s have a look at our service providers code that we wrote for getting the answers. If we look at the service provider management codeunits as implementations with specific to provider details, what is the common function that a service provider codeunit has? That’s right, the GetAnswer function. Our interface for fake AI service providers will look as follows.
The important part here is that the method signature - the procedure name, the parameters, and the returned value - must be exactly the same as we would expect it in our implementations. The visibility of a procedure will always be public there. We need interfaces only to define how our system will be communicating with an implementation. We won’t be able to see private methods anyway.
Our providers services codeunits have procedures with the method signatures from the interface and therefore we can say that they implement it. We can explicitly say so in the program code by adding the implements keyword.
After rewriting our main codeunit, the code looks as below.
We have defined a new variable of type Interface and assigned an implementation codeunit to it depending on the Provider parameter. Once we have an implementation assigned, we can call the GetAnswer method. Notice that we do it using the interface variable which at the moment of calling the method has a reference to the relevant instance of the implementation codeunit. Can we use this for extending our solution for the customers? Exposing an event sounds like a good option, so let’s do it!
All our customers have to do now is to extend the enum 50100 "ASH Fake AI Service Providers" and subscribe to the new handy event where they can assign their implementations to the AIServiceProvider variable.
Creating an extension to the main app
To demonstrate that, let’s create a separate app for the new fake AI service provider from the company called Apricot, and name the app Custom AI Provider. We want it to be automatically integrated into our Fake AI Service page once the app is installed.
After we have set up the dependency and the workspace for the two apps, we can define the new enum extension as follows.
The Apricot service provider codeunit needs its own interface implementation and we will complete the integration of the custom app via the event subscription.
Once the second app is also published, the custom implementation can now be tested along with the built-in ones, and we get the expected response.
Phew, a customized code base is no longer required and the future looks brighter. Our Customers are still looking forward to doing business together with us!
What we have done now is applying the Dependency Inversion principle, one of the SOLID principles of software design. Instead of making our base product code dependent and tightly coupled with the customers code, we let customers’ solutions to depend on ours and inverted the dependency. This approach allows us to develop our features without really having to know about the custom logic for service providers. All we needed to do is to let the custom solutions know what our product expects them to do - provide a GetAnswers method implementation - and that we did with the interface definition. All customers needed to do is to have implemented the interface. Everyone can do what they want now without interference as long as the system communication contract is fulfilled.
Mapping interfaces with implementations
Although we’ve sold a couple of program code management problems, our main codeunit "ASH Fake AI Service Mgt." still needs updating when a service provider is added to our base product. There is a neater way to map interfaces with implementations that will let us completely free the codeunit from references to specific AI provider services codeunits and related to them logic leaving only reusable program code. We will look into it next.
The fact is that Enums in AL also can implement interfaces. Well, not exactly but with Enums we can further reference an implementation against each value. There is also the DefaultImplementation Enum property that can be used for all values without a specific Implementation in case we need it. Let’s see how we can use the enum 50100 "ASH Fake AI Service Providers" more effectively.
With this approach, we can further clean up our main codeunit 50100 "ASH Fake AI Service Mgt." and remove all the implementation variables which we mapped in the Enum object.
The dependent app Custom AI Provider has broken with the change - it also requires the enum extension update.
With this change, we no longer need the event and the subscriber code, all implementations are now mapped via the "ASH Fake AI Service Providers" enum and we can remove the redundant program code - delete entire codeunit 50151 "ASH Cust. Serv. Provider Mgt." and remove the OnBeforeGetAnswer event and related code from the codeunit 50100 "ASH Fake AI Service Mgt.". The main codeunit looks as lean as never before!
There is actually no essential need for the main codeunit 50100 "ASH Fake AI Service Mgt." now. We can assign the enum to the interface directly in the page action if you wish to go further.
At this point, we have rewritten both of the apps to use Enum as the implementation mapper. The main codeunit no longer needs to know about any implementation details of the service. All it is responsible from now on is only processing whatever we receive from the service calls. If we need to add a new service provider, we would only need to add it to the Enum object and provide the interface implementation. The service page and the main codeunit is now reused across entire functionality when we need to add new service providers or update their system logic.
An example from Business Central apps
Every time I need to learn more about using AL types or find code samples, I tend to look into the standard Microsoft applications first. There is a great example for interfaces there - the Email Connector interface. It’s defined in the System Application and implemented in dedicated to a particular connector apps, for instance, SMTP or Microsoft 365. The below is a screenshot from my environment.
When we use the Set Up Email page, where do you think the list items come from?
You’re right, from the installed Email apps. By this time, you already know how those items automatically appear there, well done! If you look into the Business Central applications source code, I’m sure you’ll find something new that we haven’t covered today.
Summary
After making a couple of impractical decisions in the beginning of our AI services app design, I’m glad that we have finally achieved the primary objective of our project. I hope that by gradually making changes to our source code, we all now have a clear understanding that the main purpose of interfaces is being able to separate implementation details from the system parts that need not be concerned with them. Especially, if we have several ways of performing the same task and we want to create a program module or an app for each of them.
We were able to enable our valuable customers to work on their own software modules extending our base product. We still could improve our application without interference due to loose coupling of our apps achieved by applying the Dependency Inversion principle.
With the interfaces concept understood and applied, we have also reduced the number of code lines and made our code more readable and maintainable.
If there are still questions on the subject or I missed anything along the way, please let me know. Happy coding!
The source code for this blog post can be found at https://github.com/ashirokikh/ashirokikh.com