Reactive Behaviors

Encapsulating discreet behaviors with Rx

Published on 14 November 2015

I am a firm believer in the notion "Everything is a stream". After all, at it's root what is a computer program but a stream of instructions toggling the state of transistors on the CPU die. As such, I have been gradually moving away from traditional imperative coding and embracing a declarative approach to implementing behavior.

Starting with the UI, my Reactive Extensions for Caliburn Micro allowed me to escape the scourge of state variables and property change non-sense and have read-model changes update the view or user input changes update the view model in a declarative fashion.

With this in place, I sort of stumbled onto a pattern I quite liked and gradually developed into a pattern I am now referring to as "Reactive Behaviors". To explain this pattern, lets take a basic example of a log in page for an application.

Traditionally, in WPF MVVM parlance, the view model for the log in page might look something like this:

public class LogInPageViewModel : Screen, ILogInPageViewModel
{
    private readonly IAuthenticationService _authenticationService;

    private readonly DelegateCommand _logInCommand;
    private readonly DelegateCommand _cancelCommand;

    private CancellationTokenSource _cts;

    private string _username;
    private string _password;
    private string _error;

    public LogInPageViewModel(IAuthenticationService authenticationService)
    {
        _authenticationService = authenticationService;

        _cts = new CancellationTokenSource();
        _logInCommand = new DelegateCommand(CanLogIn, PerformLogIn);
        _cancelCommand = new DelegateCommand(_ => true, CancelLogIn);
    }

    public void Dispose()
    {
        if (_cts != null)
        {
            _cts.Cancel();
            _cts.Dispose();
            _cts = null;
        }
    }

    private bool CanLogIn(object parameter)
    {
        return (!string.IsNullOrWhiteSpace(_username) && !string.IsNullOrWhiteSpace(_password));
    }

    private async void PerformLogIn(object parameter)
    {
        AuthenticationResponse response = await _authenticationService.AuthenticateAsync(new AuthenticationRequest(_username, _password), _cts.Token);

        if (response.Successful)
        {
            TryClose(true);
        }
        else
        {
            Error = response.Error.Message;
        }
    }

    private void CancelLogIn(object parameter)
    {
        TryClose(false);
    }

    public string Username
    {
        get { return _username; }
        set
        {
            if (!string.Equals(value, _username))
            {
                _username = value;

                NotifyOfPropertyChange(() => Username);

                _logInCommand.RaiseCanExecuteChanged();
            }
        }
    }

    public string Password
    {
        get { return _password; }
        set
        {
            if (!string.Equals(value, _password))
            {
                _password = value;

                NotifyOfPropertyChange(() => Password);

                _logInCommand.RaiseCanExecuteChanged();
            }
        }
    }

    public string Error
    {
        get { return _error; }
        private set
        {
            if (!string.Equals(value, _error))
            {
                _error = value;

                NotifyOfPropertyChange(() => Error);
            }
        }
    }

    public ICommand LogInCommand
    {
        get { return _logInCommand; }
    }
}

As we can see, we have a bunch of state variables that are updated from property setters and when certain state variables are updated we need to perform specific actions. Now, can you quickly identify all the behaviors of this view model?

Well, it's not a complicated view model so it'll probably just take a few minutes to work out the behaviors. To summarize, they are:

  • When the user has entered a value in both the user name and password text boxes then enable the "Log in" button
  • When the user clicks the "Log in" button, asynchronously attempt to log in with the supplied credentials.
  • When a successful login attempt is made, close the dialog
  • When an unsuccessful login attempt is made, display an error
  • When the user clicks the "Cancel" button, close the dialog

But that took a few minutes of scanning back and forth over the class right? Also, while we have been good developers and applied DRY by centralizing the logic to determine when the log in button should be enabled, in order to know under what circumstances the logic will be called, we need to perform a "Find References" on the method. The cause and effect of this behavior are disassociated. In fact, in this instance it's event worse, as this method is only passed to the DelegateCommand instance, we have to know that the DelegateCommand will invoke this method everytime the RaiseCanExecuteChanged is called and then perform another "Find References" to find all the locations RaiseCanExecuteChanged is called. Phew!

Wouldn't it be better if we could somehow centralize this logic in discreet methods with names that describe exactly the behavior of the class? Ladies and gentlemen, I present to you the same class re-written using "Reactive Behaviors":

public class LogInPageViewModel : Screen, ILogInPageViewModel
{
    private readonly IAuthenticationService _authenticationService;

    private ObservableProperty<string> _username;
    private ObservableProperty<string> _password;
    private ObservableProperty<string> _error;
    private ObservableCommand _logInCommand;
    private ObservableCommand _cancelCommand;

    private Subject<AuthenticationResponse> _logInResponse;

    private IDisposable _behaviors;

    public LogInPageViewModel(IAuthenticationService authenticationService)
    {
        _authenticationService = authenticationService;

        _username = new ObservableProperty<string>(this, () => Username);
        _password = new ObservableProperty<string>(this, () => Password);
        _error = new ObservableProperty<string>(this, () => Error);
        _logInCommand = new ObservableCommand();
        _cancelCommand = new ObservableCommand();

        _logInResponse = new Subject<AuthenticationResponse>();

        _behaviors = new CompositeDisposable(
            WhenTheUserHasEnteredBothUsernameAndPasswordThenEnableLogInButton(),
            WhenTheUserClicksTheLogInButtonAttemptToLogIn(),
            WhenASuccessfulLogInAttemptIsMadeCloseTheDialog(),
            WhenAnUnsuccessfulLogInAttemptIsMadeDisplayTheError(),
            WhenTheUserClicksTheCancelButtonCloseTheDialog()
        );
    }

    public void Dispose()
    {
        if (_behaviors != null)
        {
            _behaviors.Dispose();
            _behaviors = null;
        }
    }

    private IDisposable WhenTheUserHasEnteredBothUsernameAndPasswordThenEnableLogInButton()
    {
        return Observable
            .CombineLatest(_username, _password, (username, password) => !string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
            .Subscribe(_logInCommand);
    }

    private IDisposable WhenTheUserClicksTheLogInButtonAttemptToLogIn()
    {
        return _logInCommand
            .SelectMany(_ => Observable.CombineLatest(_username, _password, (username, password) => new AuthenticationRequest(username, password)).Take(1))
            .SelectMany(request => _authenticationService.AuthenticateAsync(request))
            .Subscribe(_logInResponse);
    }

    private IDisposable WhenASuccessfulLogInAttemptIsMadeCloseTheDialog()
    {
        return _logInResponse
            .Where(response => response.Successful)
            .Subscribe(response => TryClose(true));
    }

    private IDisposable WhenAnUnsuccessfulLogInAttemptIsMadeDisplayTheError()
    {
        return _logInResponse
            .Where(response => !response.Successful)
            .Select(response => response.Error.Message)
            .Subscribe(_error);
    }

    private IDisposable WhenTheUserClicksTheCancelButtonCloseTheDialog()
    {
        return _cancelCommand
            .Subscribe(_ => TryClose(false));
    }

    public string Username
    {
        get { return _username.Get(); }
        set { _username.Set(value); }
    }

    public string Password
    {
        get { return _password.Get(); }
        set { _password.Set(value); }
    }

    public string Error
    {
        get { return _error.Get(); }
    }

    public ICommand LogInCommand
    {
        get { return _logInCommand; }
    }

    public ICommand CancelCommand
    {
        get { return _cancelCommand; }
    }
}

As shown, the constructor instantiates a CompositeDisposable containing calls to methods implementing the behavior of the class and which return an IDisposable that, when disposed, will tear down the behavior. Each method is named after exactly one desired behavior and comprises a subscription to all the required inputs for the behavior, a series of projections which implement the behavior and, finally, a subscription which surfaces the result of the behavior.

Lets take the example of the WhenTheUserHasEnteredBothUsernameAndPasswordThenEnableLogInButton method. Here we subscribe to both the _username and _password properties, and use a selector function to return a boolean indicating whether they're both populated. The _logInCommand subscribes to the result which will enable or disable the command (by raising CanExecuteChanged events) appropriately.

Furthermore, by leveraging Observables we become thread safe. It doesn't matter on which thread the source properties are updated from, the behavior will be performed and output applied without the risk of missing values or race-conditions between updates to the appropriate inputs.

With behaviors implemented in this fashion, behavioral unit testing becomes incredibly obvious. Simply copy the behavior method names into your test fixture and assert that it performs as expected.

I believe this pattern provides numerous benefits:

  • Promotes behavior driven development and unit testing.
  • Promotes functional and thread safe programming practises.
  • Reduces the risk of (and if done well, can eliminate) side effects as specific behaviors are isolated in a single well named method.
  • Stops 'code rot' as all behavior is encapsulated within specifically named methods. Want new behaviour? Add a new method. Don't want a specific behavior anymore? Just removed it. Want a specific behavior to change? Change the one method and know that you haven't broken anything else.
  • Provides concise mechanisms for aggregating multiple inputs and promotes asynchronous processes to first-class status.
  • Reduces the need for utility classes as data can be passed through the pipeline as strongly typed anonymous classes.
  • Prevents memory leaks as all behaviors return a disposable that when disposed removes all subscriptions and disposed all managed resources.

While this example targets the UI and leverages Caliburn Micro with Reactive Extensions for projection to IObservable instances (and property change notification), this pattern can be employed across any class which treats inputs and outputs as a stream.

I have been employing this pattern very successfully across a variety of functional layers for quite some time now. I'd be really interested to hear your thoughts.

Code for above examples available on Github