次の方法で共有


Dynamics AX eventing

Abstract:

The following blog presents two useful ways to perform communication between classes in Dynamics AX. An event mechanism is presented for this, which, if adopted by the application development community, has the potential of making the ISV’s life a little easier because the interfaces that he can react to are more clearly delimited. The second mechanism described will provide loosely coupled classes, which is useful for eliminating some of the many dependencies that currently exist between the Dynamics classes. Both ideas are implemented in the enclosed .xpo file. Please note that the code herein is not to be considered production code – I’m sure it contains quite a few best practice violations – It is intended to start a discussion on this topic.

Introduction

Eventing mechanisms are used in many software applications, and are known under a variety of names. Often eventing models are known under the names of “publish and subscribe” or “observer”, models, see https://en.wikipedia.org/wiki/Observer_pattern. This pattern is used extensively in the .NET, where events (or multicast delegates) are the first class citizens used to provide behavior to UI elements.

As it happens, this pattern is very easy to implement in X++. In fact, the method used here does not depend on any features that have not always existed in Dynamics. At the high level, an event class instance is introduced for each event that a given class or table wishes to notify the environment about. Whenever the particular information is ready in the given context, the event is fired (i.e. the information is published), and any number of listeners (or subscribers) can react to it. The event carries with it a payload of information that the client code can use for whatever purpose.

                                                               

For instance, let’s consider the application code that posts invoices. When the invoice is posted, the code calls the InvoicePosted event, to which it supplies a payload containing the number of the invoice, the amount invoiced, and any other information that the observers would need. An ISV is working with a customer that wants to transmit this information to a legacy system: He writes code that establishes that he is interested in being notified about postings (in other words, he subscribes to the event), and he implements code in the QueuePosting method in the Legacy class (called whenever the invoice module is done posting, as described) that writes the pertinent information to a message queue, from where the information is picked up by the legacy system.

The implementation

The events are implemented in the SysEvent application class. This class contains code to add a subscriber (which is what the code that is interested in receiving notifications will do). A subscriber is identified by the object to call back on, and the class and method names identifying the method to call on that object. For instance, assume that InvoicePosted is an event (of type SysEvent) and that the QueuePosting method on the class called Legacy needs to be notified when an invoice is posted. The code would look something like:

InvoicePosted.AddSubscriber(legacyApp, ClassStr(Legacy),
MethodStr(Legacy,QueuePosting))

Static calls are possible too, by specifying a null object, in which case the (classname, methodname) must denote a static method. The current implementation requires the callback methods to have a specific parameter profile:

void MyCallback (object sender, SysEventArgs args)

When the event occurs, the code notifies the event’s subscribers by calling the Publish method on the event. The sender may be passed to identify the origin of the event (accessible by the handling code as the sender argument), and an object containing other information may be passed as arguments (in the args argument), typically in a class derived from SysEventArgs.

The current implementation as described here in all its simplicity does have some disadvantages. When a subscriber is registered it is done by giving the name of the class and the name of the method on that class as arguments to the AddSubscriber call. In order to achieve some compile time safety, the ClassStr and MethodStr intrinsic methods should be used to designate class names and method names respectively, as shown in the example above. However, the user may still provide different class names in the argument for ClassStr and to first argument to MethodStr. If this is considered a major issue, a new intrinsic function can be introduced to remedy the situation. So, instead of

InvoicePosted.AddSubscriber(legacyApp, ClassStr(Legacy),
MethodStr(Legacy,QueuePosting))

you would write

InvoicePosted.AddSubscriber(legacyApp, CallbackMethod(Legacy, QueuePosting))

where CallbackMethod is an intrinsic function that checks that first argument does indeed denote an existing class (Legacy in casu), that it has a method called QueuePosting, and that QueuePosting has the required parameter profile (returning void, having two formal parameters, one of type object and the other being a type derived from SysEventArgs). In the current implementation, all this checking is done at runtime, which is wasteful. While this intrinsic function is not difficult to add to the kernel, doing so will not make it available in all the Dynamics installations already installed.

While this model may seem a little cleaner than the derivation based approach we use today (and is much closer to what a .NET programmer might expect), it does not solve the problem that the subscriber must know details about the publisher; it must after all know that it has a particular event, and must access it to subscribe to it. In order to achieve a more loose coupling between the publisher and the subscriber, you would need to introduce an event broker, that resides on some globally accessible object (like the ClassFactory object for instance). The subscriber informs the event broker what events it wishes to subscribe to, by using a string to designate the event (“InvoicingComplete” in the example below). The publishing and the subscribing code do not need to know one another – The fact that they both know the event broker and the name of the event is enough to enable the eventing.

Please find enclosed an example of use of this technique:

static void DemoEventBroker(Args _args)

{

// This is done at Dynamics startup. The broker is stored on
// the globally accessible ClassFactory object.

    SysEventBroker broker = new SysEventBroker();

    // These calls are made by the subscribers to inform the
// broker what they want to be notified about. In this case
// the subscriber wants to be notified when someone calls the
// InvoicingComplete event.

    // In reality this would be ClassFactory.broker.AddSubscriber(…)

broker.AddSubscriber("InvoicingComplete", legacyApp,
ClassStr(Legacy), MethodStr(Legacy,QueuePosting));

    // This is done by the invoicing module when it is done invoicing:
// (Again, this should read ClassFactory.Publish(…)). The args would be
// initialized with relevant information about the invoice. The module
// performing the invoice can identify itself through the sender args
// (null in this case).

    broker.Publish("InvoicingComplete", null, new SysEventArgs(…));

}

I have enclosed code to demo this in the enclosed package.

PrivateProject_Events.xpo

Comments

  • Anonymous
    June 24, 2007
    See also ClassesSysDelegate in Ax3

  • Anonymous
    April 02, 2009
    I have publish a similar approach, see link. I am afraid I did not search first, so I did not see your contribrution until now. The naming of thins is very similar. One difference in my solution is the possibility to select on the sender. I will make a back link to your solution.