pondelok 5. januára 2015

Message oriented development

Introduction

Happy New Year 2015! As usual start of new year is place to think about what went good and what went wrong in last year. For me last year was time when I felt limitations of OOP and I started rethink my approach to development and looked into alternative methods to achieve goals faster and better. I started to use lambdas and asynchronous chaining more, looked into functional programming approach in F# - highly recommended to at least try, and, as answer to my dissatisfaction in bigger front-end application project, tried to find out better way how to create software. I received initial impulse by reading article Object Oriented Programming is an expensive disaster which must end and I would build on some of the observations mentioned there.

Now, after 6 months of being part of the team working on this particular piece of software using alternative method I would like to share my story and outline architectural decisions and balance out benefits with constraints. I would like to point out first that there is no silver bullet in SW development and as all approaches this one has it drawbacks and cannot be applied across all projects.

Motivation

Last months I’m dealing with desktop application development mostly on frontend using C#, WPF and DirectX (but I believe that experience can easily translate to other languages/frameworks) and approximately after 3 months we started hitting first walls using OOP approach. Our target was to have unit-testable code that has good separation of responsibilities but somehow we always ended up in quite messy entanglement of code where any change was causing unpredictable side effects.

In following article I will try to list out conceptual issues that rises from using “traditional OOP” approach in frontend applications; of course those ideas are not new (I was able to find that this concept was mentioned by Alan Key as Actor Model) but I think they provide nice complement to OOP deficiencies.

One target to rule them all

Applications (especially frontend applications) very rarely have just one point of entry for actions. For example action Close application has usually multiple entry points - actions passed from operating system (user presses Alt+F4) and inside application code (user selects File->Exit from main menu). As developers we would like to have unified way how to handle this situation (e.g. check if there are unsaved data available and prompt user with Do you want to save your data? dialog before exiting).

Naturally we would like to have separate component that deals with this workflow in one place, let’s say DataManager:

public class DataManager
{
    public void CheckSaveBeforeExit()
    { /* Do your stuff */  }
}

However, how we are going to invoke this method? Usually code dealing with window management is in different class then one dealing with menu so we need to have instance of DataManager in both classes in order to invoke the method. We don’t want to create multiple DataManagers, singleton is not a good idea since it kills the unit testing so we have to either pass DataManager through property or constructor:

public class MyWindow
{
    private DataManager _dataManager;

    public MyWindow(DataManager dataManager)
    {
        _dataManager = dataManager;
    }

    protected void OnExit()
    {
        _dataManager.CheckSaveBeforeExit();
    }
}

This of course effectively prevents us to perform unit testing - in unit tests we don’t want to save data to HDD every time we pefrom the test but we would like to examine data for validity. Latest response to this issue is to come up with interface to be able to mock up DataManager:

public interface IDataManager
{
    void CheckSaveBeforeExit();
}

Now retrieve real implementation via IoC and configure IoCManager in some startup configuration file (for simplicity directly in code but this can be done by injecting into constructor or property):

protected void OnExit()
{
    IoCManager.GetInstance<IDataManager>().CheckSaveBeforeExit();
}

Please note amount of boilerplate code that we need to do in order to support this very common scenario while giving us fragile and hard to reuse structure - IDataManager will most probably also contain other methods (Load, Save, Reset etc.) which are completely irrelevant and there is no clear indication which parts of IDataManager are required. Worse yet DataManager will probably have dependencies on it’s own (database provider).

State is your enemy

Remembering and relying on state setup by other action one of the worst things that can be done in SW development since very few operations are strictly tied to one state. Continuing from example above while writing Load() method we will most probably would like to perform it in separate thread so we can keep UI responsive but we would also like to update UI components to be disabled.

OOP guides us to provide IsLoading accessor which will indicate if data are loaded:

public class DataManager
{
    private bool isLoading;
    public book IsLoading { get { return isLoading; } }

    public void Load()
    {
        isLoading = true;

        // ... Load your data asynchronously

        isLoading = false;
    }
}

Any other object that will try to observe this accessor and mutate it’s state based on this value (e.g. disable the button, reset the undo stack etc.) is creating another dimension of complexity in your code since the value of IsLoading can be changed in the instant program control leaves the IsLoading statement - DataManager is no longer loading data but button is still disabled.

To counteract this behavior you actually need to build some kind of Observer pattern - please note that promise of accessor (access the object data safely) is not kept and developers needs to create plenty of boilerplate code.

You wanted a banana but what you got was a gorilla holding the banana and the entire jungle

by Joe Armstrong

Let’s imagine scenario with class encapsulating raster graphics device (e.g. Window) and Rectangle - class that draws the rectangle to the Window using Draw() method. Per OOP philosophy Window should contain accessors (e.g. ones that holds size of the window using properties Width and Height) and actions of them have Draw() method which does the drawing. We derive MyWindow and implement the requirements. In our scenario this can be nicely demonstrated by requirement to draw rectangle into center of window.

First approach could be that instance of Rectangle is created inside MyWindow, positioned properly before each draw. Later on we find out that rectangle should not be displayed while loading the data and if data is corrupted it should be actually half the size and with red border. All this logic goes into MyWindow and it’s harder and harder to maintain so we want to separate all logic relative to this rectangle into own class MySpecialRectangle. The question is how to pass information about Window size in order to keep Rectangle centered:

  • Option #1 - passing whole Window object to Draw method; now we need to ensure that we actually have valid instance passed (at least check for null). This however goes against reusability - what about case that we would like to draw same Rectangle to different container component (e.g. Grid)? Procedure of drawing of rectangle is the same but we are now coupled to Window-related environment.
  • Option #2 - OK, let’s separate what we need into interface IUIComponent, implement this on Window and Grid. How to pass this interface to the MySpecialRectangle - we can e.g. pass it in constructor or pass it as property (and again face the possiblity that developer using this class in other context will forget to initialize properly).
  • Option #3 - pass the relevant data in constructor (pass two numbers indicating ParentWidth and ParentHeight) - how to then take care of possible Window size update when user resizes the window? We need to create new method OnSizeUpdate, OnLoadingUpdate, OnLoadingErrorUpdate and since loading of data is done in separate class MyWindow needs to listen to those changes and forward it to MySpecialRectangle.

In all cases we either end up solving problem with contextual class and not focusing on what our class should do. This is caused by a fact that we are trying to pass too much of the data - our class consumes only very specific subset of information however OOP doesn’t give us good tool how to pass just information that we are interested in.

Message oriented development

At core of all issues mentioned above are two facts:

  • OOP promotes “data with actions” approach
  • OOP doesn’t allow easy messaging

To counter this we are using following design principles when creating new classes:

  • No public accessors on components and limit public methods to minimum (e.g. to use 3rd party libraries)
  • Pass information only by using immutable messages and without knowing the sender
  • Prefer immutable data containers with no methods
  • Attach to relevant messages in constructor

To support this we are using Messenger class of MVVM Light framework (but this can be achieved also by custom messenger implementation). The idea is that each change in application should be distributed by specific message holding data that cannot be changed during processing.

Loose coupling invocation

When Window detects change of size it will Send new WindowChangedMessage which will contain readonly properties exposing updated Width and Height and Rectangle will Register to consume this message in constructor:

public class WindowChangedMessage
{
    public int Width { get; private set; }
    public int Height { get; private set; }

    public WindowChangedMessage(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

public class Window
{
    // Called when size is updated
    protected void OnSizeUpdate(int width, int height)
    {
        Messenger.Default.Send(new WindowChangedMessage(width, height));
    }
}

public class Rectangle
{
    protected Point position;

    public void Draw()
    {
        // Draw rectangle at position
    }
}

public class MySpecialRectangle : Rectangle
{
    public MySpecialRectangle()
    {
        Messenger.Register<WindowChangedMessage>(this, OnWindowChangedMessage);
    }

    private void OnWindowChangedMessage(WindowChangedMessage message)
    {
        // Calculate new position to appear in center
        position = new Point(...);
    }
}

Note that classes are completely isolated and they have no direct knowledge about each other (compared to e.g. C# events) - each class holds all information that it needs for it’s proper work and can subscribe to additional messages.

Same approach can be utilized for loading sequence:

public class LoadDataMessage
{ /* No data needed */ }

public class DataLoadedMessage
{ /* No data needed */ }

public class DataManager
{
    public DataManager()
    {
        Messenger.Register<LoadDataMessage>(this, OnLoadDataMessage);
    }

    private void OnLoadDataMessage(LoadDataMessage message)
    {
        // ... Load data

        Messenger.Default.Send(new DataLoadedMessage());
    }
}

public class Rectangle
{
    protected bool isVisible;

    public void Draw()
    {
        if (isVisible)
        {
            // Draw rectangle at position
        }
    }
}

public class MySpecialRectangle : Rectangle
{
    public MySpecialRectangle()
    {
        Messenger.Register<LoadDataMessage>(this, OnLoadDataMessage);
        Messenger.Register<DataLoadedMessage>(this, OnDataLoadedMessage);
    }

    private void OnLoadDataMessage(LoadDataMessage message)
    {
        isVisible = false;
    }

    private void OnDataLoadedMessage(DataLoadedMessage message)
    {
        isVisible = true;
    }
}

This arrangement clearly points out to business rules attached to MySpecialRectangle - we don’t care who invoked the LoadDataMessage (it can sent at start of application or by user action to reload the data). If we want to change behavior of MySpecialRectangle we don’t need to browse through multiple classes and look from which context other classes sets isVisible - it’s set only in one class.

Chaining

It’s very easy to chain messages to achieve sequence of actions - for example if user clicks on Close button we would like to check if data needs to be saved. However user can save data also by selecting File->Save:

   [click Exit]           [click Save]
        |                      |
        v                      v
RequestExitMessage +->  SaveDataMessage --> DataSavedMessage -\
                    \------------------------------------------+> ExitApplicationMessage

We need to know context or continuation of action which can be reinforced in constructors:

public class RequestExitMessage
{ }

public class SaveDataMessage
{
    public bool ExitAfterSave { get; private set; }

    public SaveDataMessage() {}
    public SaveDataMessage(RequestExitMessage message) { ExitAfterSave = true; }
}

public class DataSavedMessage
{
    public bool ExitAfterSave { get; private set; }

    public DataSavedMessage() {}
    public DataSavedMessage(SaveDataMessage message) { ExitAfterSave = message.ExitAfterSave; }
}

public class DataManager
{
    private bool isDirty;

    public DataManager()
    {
        Messenger.Register<RequestExitMessage>(this, OnRequestExitMessage);
        Messenger.Register<SaveDataMessage>(this, OnSaveDataMessage);
        Messenger.Register<DataSavedMessage>(this, OnDataSavedMessage);
    }

    private void OnDataSavedMessage(DataSavedMessage message)
    {
        if (message.ExitAfterSave)
        {
            Messenger.Default.Send(new ExitApplicationMessage());
        }
    }

    private void OnRequestExitMessage(RequestExitMessage message)
    {
        if (isDirty)
        {
            Messenger.Default.Send(new SaveDataMessage(message));
        }
        else
        {
            Messenger.Default.Send(new ExitApplicationMessage());
        }
    }

    private void OnSaveDataMessage(SaveDataMessage message)
    {
        // Save data
        Messenger.Default.Send(new DataSavedMessage(message));
    }
}

By creating messages in this manner consumer of message will be able to quickly determine context of message - SaveDataMessage should be either constructed without parameters or developer should use RequestExitMessage to get proper chaining.

This example also shows that we are dealing with only one, very specific aspect of saving the data - actual saving to HDD. We are not mixing how UI is changed (indication that data are saved), we don’t care about how actual application exit looks like (close all windows etc.). Those aspects of this message can be implemented in separate classes by observing same messages. Better yet, we can very easily remove whole Save feature as long as we keep correct sequence of messages (e.g. in demo application).

Response messaging

So far we covered only fire-and-forget type of message when we actually doesn’t care about result value from the other component; in case that we need to get some values from receivers we need to incorporate two or more messages.
For example let’s imagine that we would like to build handling of click events from active components on the screen. Normally this goes in three steps:

  1. When mouse goes down determine if there is some element under mouse position
  2. When mouse goes up test if mouse position is still within the same element (if user moves the mouse outside element where it started the click and release it should not be considered as click)

Let’s create ClickHandler class which will receive mouse signals from OS and Button classes that will contain information about active element. Since we don’t want to expose internal values of elements we will share only necessary amount of information in following sequence of calls:

  1. ClickHandler will send HitTestMessage on mouse down with position to all elements to determine if any of them contains mouse position; if yes element will send HitTestPassedMessage with it’s identifier and valid area for element
  2. ClickHandler will store HitTestPassedMessage (if any)
  3. On mouse up ClickHandler will test if mouse position is still within element’s valid area and send ClickMessage with ID of element
  4. Element will process ClickMessage and check if it’s ID is matching message ID
public class HitTestMessage
{
    public Point Position { get; private set; }
    public HitTestMessage(Point position) { Position = position; }
}

public class HitTestPassedMessage
{
    public Rect ValidArea { get; private set; }
    public string Id { get; private set; }

    public HitTestPassedMessage(string id, Rect validArea) 
    {
        Id = id;
        ValidArea = validArea;
    }
}

public class ClickMessage
{
    public string Id { get; private set; }
    public ClickMessage(string id) { Id = id; }
}

public class Button
{
    protected readonly string _id;
    private Rect _rectangle;

    public Button()
    {
        _id = Guid.NewGuid().ToString();
        Messenger.Default.Register<HitTestMessage>(this, OnHitTestMessage);
        Messenger.Default.Register<ClickMessage>(this, OnClickMessage);
    }

    private void OnHitTestMessage(HitTestMessage message)
    {
        if (_rectangle.Contains(message.Position))
        {
            Messenger.Default.Send(new HitTestPassedMessage(_id, _rectangle));
        }
    }

    private void OnClickMessage(ClickMessage message)
    {
        if (message.Id == _id)
        {
            // Perform click action
        }
    }
}

public class ClickHandler
{
    private HitTestPassedMessage _lastMessage;

    public ClickHandler()
    {
        Messenger.Default.Register<HitTestPassedMessage>(this, OnHitTestPassedMessage);
    }

    public void OnHitTestPassedMessage(HitTestPassedMessage message)
    {
        _lastMessage = message;
    }

    private OnMouseDown(Point position)
    {
        Messenger.Default.Send(new HitTestMessage(position));
    }

    private OnMouseUp(Point position)
    {
        if (null != _lastMessage)
        {
            var message = _lastMessage;
            _lastMessage = null;

            if (message.ValidArea.Contains(position))
            {
                Messenger.Default.Send(new ClickMessage(message.Id));
            }
        }
    }
}

I would like to again point out complete isolation between classes - developers can easily create unit tests and send/observe messages and test proper behavior of the ClickHandler without creating mocks, configuring any IoC containers or other constructs. It’s also worth to mention here that with this architecture it’s easy to convert mouse handling in Button to separate component and use it as mixin for any rectangular-shaped active area.

Scalability/Extendability

Couple of additional points favoring this style of development:

  • It’s very easy to convert actions to asynchronous execution if needed without changing calling sequence (compared to e.g. await/async methodology which will at least require all callers to include await before invoking the method). In DataManager save example developer can easily convert OnSaveDataMessage to perform action asynchronously without having to worry about other components:
private void OnSaveDataMessage(SaveDataMessage message)
{
    Task.Factory.StartNew(message => {
        // Save data
    }).ContinueWith(task => {
        Messenger.Default.Send(new DataSavedMessage(message));
    });
} 
  • It’s also easy to reuse developed components to other project - we just need to extract related components and related messages
  • Due to nature most of the application is stateless so it can be easily transformed to multi-core or even multi-computer application by sending/receiving messages by inter-process messaging zeromq or via sockets.

Žiadne komentáre:

Zverejnenie komentára