15.10.2016, 01:36 | #1 |
Участник
|
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:
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:
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:
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 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:
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:
However, we still have room for improvement:
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:
(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, напишите личное сообщение администратору. |
|
|
Опции темы | Поиск в этой теме |
Опции просмотра | |
|