AXForum  
Вернуться   AXForum > Microsoft Dynamics NAV > NAV: Blogs
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 15.10.2016, 01:36   #1  
Blog bot is offline
Blog bot
Участник
 
25,617 / 848 (80) +++++++
Регистрация: 28.10.2006
Navigate Into Success: Module Binder Pattern proposal
Источник: http://vjeko.com/module-binder-pattern-proposal
==============

Whoa! This has been quite an event, the Directions EMEA 2016 in Prague. There has never been this many people (1.700+) and it was quite a pleasure connecting again with old friends, and meeting new friends. Also, it has been quite a pleasure listening to many good sessions, and an even bigger pleasure delivering four of them.

And this is why I am blogging now – to follow up on my promise during my Polymorphic Event Patterns for C/AL. I promised you that I’d post my pattern proposal online, and here I am doing it.

Let’s get started.



In my three earlier posts, I have talked about loose coupling and polymorphism in C/AL:

In these three posts I have explained the problem we have when we want to decouple dependencies in C/AL, and about limitations  we face inside the platform that isn’t quite friendly towards loose coupling.

Loose coupling is possible in C/AL, but whichever way you look at it, you have to cut corners. Either you get loose coupling or you get state preservation, but you don’t get both.

Being stubborn and all, and being a total cake junkie, I set out to find a way to actually have my cake and eat it. And here I present what I named “module binder” pattern, for total lack of creativity. So, here it is.





Introduction

The “handled” pattern offers the best loose coupling capabilities of all design patterns in NAV. And yet, it has quite a few shortcomings:

  • It mixes up infrastructure and business logic code
  • It does not preserve state
  • It is a broadcast, rather than loose coupling
  • It depends on a gentlemen’s agreement between publisher and subscribers (that is: that all subscribers will behave fairly, will properly identify themselves, observe the infrastructure requirements and generally behave as expected)
The “Module Binder” pattern attempts to address these issues:

  • It clearly separates business logic from infrastructure
  • It allows state preservation
  • It is not a broadcast, and is a real loose coupling pattern
Unfortunately, at a thin level it still has a sort of a gentlemen’s agreement, but at a thinner level, that’s easier to enforce.

Interface

Interface, or façade, is the same kind of a component as was used in other façade-based patterns I wrote bout in the previous posts (arguments table façade and TempBlob façade).

So, unsurprising, this interface is a codeunit that exposes a public method. This public method is the façade that channels the call to the correct type of dependency. Unlike other facades we saw earlier, this one does not need to execute any business logic, like finding a correct dependency or anything, it merely needs to raise an event, like this:



Can it get any simpler than this?

Probably not, but I’ll complicate it a slight bit later on, just not quite yet.

What you can see here is that the published event does not have any parameters related to infrastructure, like the “handled” pattern used to have. We don’t set pass a boolean value indicating whether event has been handled, nor we pass any subscriber identification. The infrastructure – which I said is completely separated from the business logic – will take care of properly binding a subscriber and making sure that the event was handled, and handled only once.

Module

Module is a codeunit pair: one codeunit in charge of business logic, and one codeunit in charge of infrastructure.

Here’s the business logic codeunit:



The business logic above is obviously “logging” events into a Twitter account, with authorization information stored in a table. The table and the .NET API I invoked here are irrelevant, they are merely to illustrate some specific business logic and what a typical business logic codeunit would look like.

Obviously, there is no infrastructure code here.

Infrastructure belongs into a separate codeunit: the module binder codeunit.

Since one of the goals of this pattern is to allow state preservation between calls, the only way to achieve that is through BINDSUBSCRIPTION function. However, BINDSUBSCRIPTION requires a codeunit variable of an exact codeunit subtype, and as soon as you declare such a variable, loose coupling is gone down the drain. So, instead of directly binding to my Twitter event logging codeunit from anywhere in the infrastructure code of this pattern, I provide an extra codeunit that handles the infrastructure for my Twitter event logging module. That’s why I call it a module – it’s not a single codeunit that handles specific business logic, but a pair of codeunits: one of them (binder, the infrastructure) is merely in charge of binding event subscriptions of my business logic codeunit. Obviously, this binder codeunit has a tight coupling to the business logic codeunit, but in this case it is not a problem, because it is the module that is loosely coupled to the system, and whether the module consists of one or more codeunits is irrelevant. If you add the module as a whole, or remove the module as a whole, and generally handle the whole module (codeunit pair) as a whole, loose coupling is always maintained.

Here’s what the binder codeunit for this Twitter codeunit would look like:



It consists of two functions:

  • BindModule: subscribes the business codeunit instance to events.
  • UnbindModule: unsubscribes the business codeunit instance from events.
Both of these functions are called with two parameters: interface name and module name. The infrastructure of this pattern (I haven’t yet talked about it) will take care of these two parameters and will send them as identification.

Also, both of these functions are event subscribers (obviously). I’ll explain what they subscribe to a little bit later. For now, just keep a mental note somewhere that the binding and unbinding of a module happens through an event infrastructure.

There are several things to note about this codeunit, and they are crucial to understand.

Firstly, this codeunit is a single-instance codeunit. Unfortunately, with the current state of affairs, it’s plain impossible to achieve anything close to full loose coupling and state preservation without using a single-instance codeunit at one level at least. However, in this pattern, I chose to do that at the infrastructure level, not the business logic level. So my module binder codeunit is single-instance, but the business logic codeunit is not. This eventually allows for having multiple separate instances of business logic codeunit all preserving different state for the purposes of different consumers. The pattern as I describe it today does not achieve this yet, but it’s only for the sake of simplicity – I did not want to overly complicate it (the pattern is complicated enough without it already). In my next post I’ll explain how to achieve multi-instance binding though a single-instance codeunit. For now, just take my word for it that this single-instance won’t be an issue in the long run.

Secondly, this codeunit handles some kind of identification. Actually, two kinds of identification: interface identification, and module identification. My example uses text for both, but it can be integers, GUIDs, whatever you wish. I chose text because it’s the most versatile of all.

Interface identification has to do with the interface itself. The whole module binder pattern infrastructure may have to handle more interfaces than one. That is – it may need to loosely bind more kinds of modules. In my example I have  a Twitter event logging module that implements the Event Logging interface. But I may have more interfaces, like Address Formatting interface, Currency Conversion interface, Pricing and Discounting interface. Any functionality that can be clearly isolated, can also be defined as an interface.

Once you have an interface, you may bind modules to interfaces. However, here we have the first real issue – we must make sure to not allow binding an address formatting module to event logging interface.

In object oriented languages, such as C#, this is achieved by implementing an interface in a class. However, in C/AL we have nothing like that, so interface implementation happens on a different level. And that level is achieved through a well-documented Discovery Event pattern. If you are not familiar with it, I strongly recommend that at this stage you jump over to NAV Patterns page and read about the Discover Event. It’s extremely powerful, and my Module Binder pattern applies it to achieve what I’ll from now on call interface implementation, even though – strictly speaking – it is not interface implementation the way a C# class implements a C# interface.

There will be two levels of discovery in this pattern: interface discovery, and module discovery.



Interface Discovery

Interface discovery is a process through which interfaces “announce” their presence to the system.

So, before the system can use an interface, such as event logging interface, it needs to be aware of that interface. This “making the system” aware happens during the interface discovery stage.

All discovered interfaces are permanently persisted in a table that contains the list of the interfaces that the application supports:



There are three fields here:

  • Interface Name: defines the name of the interface. Each interface defines its own name for itself. It could have been a GUID, or an option, or an integer, but I prefer text because it’s human-readable, and when you look at the list of interfaces in this table, you see immediately what the interface is about – it’s clear from its name. This field is also the primary key field.
  • Module Name: this is the name of the module that is bound to the interface. For example, we may bind a Twitter module, or SQL module, or REST module, or File System module, or Windows Event Log module, to the Event Logging interface. Here, in this field, we specify which exact module we are binding to this specific interface. Again, it could have been GUID, or integer or something, but I chose text for human-readability.
  • Configuration Page ID: if a module requires some configuration to operate, this field would contain the ID of the page used to configure the module. Twitter may require different configuration than REST, than SQL, than File System. Therefore, this field may contain different value. This value with come from the module that’s bound.
Now, obviously, this is a very simple configuration table. It binds a module to an interface on a system level. We could make it more versatile by adding User ID, Company Name, Date From, Date To, or any other number of fields to allow further flexibility regarding which interface will be bound under specific circumstances. If you need that, you may add it to this table and then to infrastructure, to make sure that correct module is bound to an interface at run time.

Interface discovery happens through the discovery event. The event is published on the Interface Setup table:



This is all business logic in here. There is the discovery event, and upon firing, each of subscribers (interfaces) needs to call the RegisterInterface function to complete the discovery. This means that each of interfaces would have to have a discovery subscriber.

So, this is what our Event Logging interface would look like after the discovery event is handled:



Now that we have interface discovery, how do we invoke it? We do it from the Interface Setup page, a page that allows configuring the interfaces:



The code in the background is simple:



So, you open the page, it starts interface identification, all interface codeunits that subscribe to the discovery event announce their presence by registering themselves in the Interface Setup table, and upon first running, this is what the page might look like:



I have three interfaces in my demo databases, but if there are more (or less) you’d see exactly those interfaces that are present in the database.

In the FOB file I provide at the end of this post, you’ll also have these three interfaces from the screenshot.

That much about interface discovery. Let’s now move on to module discovery.



Module Discovery

Module discovery stage is in charge of discovering which modules can be bound to which interfaces. As I said, you probably don’t want to bind Twitter Event Logging module to Address Formatter interface, so at some stage something must take care of it.

It’s one of the tasks of the Module Discovery stage.

During module discovery, the infrastructure will perform another discovery event, this time passing the interface name as a parameter, and literally asking: “which modules can handle this interface”?

Again, I’ll have a setup table. The table seems to be the same as the table in the interface discovery stage:



However, the primary key is different: it is composite over the Interface Name and Module Name fields. Yes, it could be achieved with one single table, but since I am not in favor of “god tables” I prefer having two tables to avoid any issues that could possibly arise from different keys. Thus – separate tables.

This table handles module discovery, so it has a similar discovery event and a similar register function:



Apparently, the discovery event is a bit different here: it passes interface name – this is because it does not just discover modules, but discovers those modules that “implement” the specified interface. Also, it passes the InterfaceModule record by reference, because this table does not need to persist any information, so will be used as temporary only. That’s why the RegisterModule function works directly on Rec variable (with implicit WITH) and interface discovery registration function used a separate table instance.

Obviously, each module should announce itself. This means that our module binder function needs to subscribe to this module discovery event:



The ImplementsInterface is a variable or contant inside this codeunit or function that specifies which interface is being implemented. This way, a module both announces its presence, and indicates which interface it implements.

Similar to interface discovery, we have a module lookup page:



… that has the following code in the background:



This code is invoked from the OnLookup trigger for the Module Name field in the Interface Setup page:



And that’s all about interface and module discovery.

With these two discovery events (and two separate discovery stages) we have now achieved a very simple way of making new interfaces available in the application (you simply import a new interface codeunit, and it will announce itself through discovery event) and binding any module to any interface it implements, and have achieved good separation of concerns between binder (infrastructure) and business logic codeunits of each module.

And finally, we get to the workhorse of the Module Binder pattern: the central infrastructure codeunit that manages binding and unbinding of modules to interfaces when the functionality is needed at runtime.



Module Manager

Module Manager is a codeunit that is a central infrastructure codeunit of this pattern.

This is what it does (for now):



It has two methods:

  • BindInterface method receives the interface name. This method is invoked when an interface is needed from a piece of code. In our example here, when some piece of code somewhere wants to use the Event Logging interface, it invokes the BindInterface method. This method finds the correct module that is bound to the specified interface and then raises the OnBindModuleEvent specifying which interface and which module are being called to service. If there is no module bound to the specified interface, this method complains loudly about it saying that the binding failed.
  • UnbindInterface does the opposite. When Event Logging is no longer needed, this method disposes of the bound module, making sure that it no longer listens to any interface events.
There are also two events. If you remember the module binder codeunit earlier, these are the events that it subscribes to.

And that’s it.

Let’s now take a look at how to consume an interface, in this example, the event logging functionality.



Consuming Module Binder Pattern

So, let’s imagine you want to log sales order release and reopen events. There are more than necessary ways to perform this customization, and for sake of simplicity, and sake of illustrating possible state preservation, I decided to customize the Sales Order page through events.

This is what I did:



When the Sales Order page is opened, the Event Logging interface is bound. This calls the module manager, which then fires the OnBindModule event. The Module Binder codeunit responds to this event, creates a new instance of the module codeunit and binds its subscriptions. From that moment on, SalesOrder_Released and SalesOrder_Reopened event subscribers will respond to corresponding events from the Release Sales Document codeunit.

These two event subscribers will log the Release and Reopen respect using whichever event logging module was loosely bound to the event logging interface.

The BindInterface and UnbindInterface act as kinds of initialization and deinitialization functions, or perhaps you could think of them as constructors and destructors for whichever class needs to be bound at runtime time.

What have I achieved here that was not achieved with patterns described earlier:

  • There is state preservation. The business logic module is able to preserve state between multiple subscriber invocations due to the fact that it was manually bound to event subscriptions through BINDSUBSCRIPTION call.
  • There is clear separation of concerns between business logic and infrastructure. Module binder does not handle anything related to business logic. Business logic codeunit is entirely unconcerned about anything infrastructure-related.
  • All components are entirely loosely bound. The binding interface and business logic codeunit is fully determined at runtime, there is no compile-time requirement towards module existence, and modules can be added during runtime and become immediately available without having to recompile anything.
That’s something we haven’t had so far with any of the patterns we’ve seen.

However, we still have room for improvement:

  • This is still a broadcast. An event is raised, and there is no guarantee that a subscriber has responded, nor that there were not multiple subscribers that responded.
  • There can be rogue subscribers that still subscribe to event statically, thus hijacking events and injecting unwanted or unexpected business logic.
  • We apparently have single-instance approach, because module binder is single instance. While business logic state can be preserved separately, it is practically impossible because the module binder can handle only one subscriber instance at a time.


Improving Module Binder

Let’s first get rid of the broadcast. I don’t want to fire an event and allow everybody and his sister to respond to this event. Also, I want to make sure that at any given time there is at least one manual subscriber listening to my events.

How do I do that?

With a little help of a very friendly virtual table called Event Subscription. This table is maintained at runtime for each session separately, and it lists all of the event subscribers that are currently listening to any of the events anywhere in the application. It contains all the information I need.

So, I’ll add a new function to my Module Manager codeunit:



Apparently, this function will check if there are subscribers to a specific event publisher function from a specific codeunits. If there are not, it’ll start fussing around.

Also, I don’t want to have any rogue static subscribers just responding to my events regardless of the infrastructure. So I have another function:



Here, I make sure that there is not a single one static subscriber. If there is one, the function complains about it, and names the perpetrator – so you know where to go to fix the issue.

Now, if an interface is concerned about the two things above, then this is how it can make sure that these two rules are observed:



And with these checks in place, you now have no-broadcast, manual-only, loosely coupled, stateful modules.

One thing that we don’t have yet is multi-instance. That is – making sure that multiple instances of the same module can be bound and actively listening at the same time, while allowing each bound module instance to preserve its own isolated encapsulated state.

That’ll be the topic of my next post because this one is already a big bite to chew.

And, as I promised, here are the goodies:

That’s all, folks. Please let me know what you think of this, and if I’ve gone completely bananas.

(Header image: “Zlata Praha” by Roman Boed, licensed through Creative Commons)

Read this post at its original location at http://vjeko.com/module-binder-pattern-proposal, or visit the original blog at http://vjeko.com. 5e33c5f6cb90c441bd1f23d5b9eeca34The post Module Binder Pattern proposal appeared first on Vjeko.com.




Источник: http://vjeko.com/module-binder-pattern-proposal
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору.
 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
Navigate Into Success: Gentlemen’s agreement pattern, or handling the “Handled” pattern Blog bot NAV: Blogs 0 04.10.2016 12:11
Navigate Into Success: TempBlob Façade: a pattern proposition Blog bot NAV: Blogs 0 03.10.2016 09:11
Navigate Into Success: Decoupling dependencies in C/AL Blog bot NAV: Blogs 0 30.09.2016 16:11
Navigate Into Success: Activity Log or Activity Lock pattern? Blog bot Dynamics CRM: Blogs 0 20.11.2015 15:49
Navigate Into Success: .NET Tips & Tricks: Mediator Pattern (on steroids) Blog bot Dynamics CRM: Blogs 0 27.01.2014 10:25

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 04:07.