Library / CLI / GUI Pattern

I may have discussed this a few years ago in a different context, but I think it bears revisiting.

Overview

There are many patterns describing how to factor code. Model-View-Controller is a classic one, but over the years multiple variations have come to exist, like Model-View-Presenter or Model-View-ViewModel.

Here I want to talk about something a bit different but related. I find doing this over and over again: first build a quick monolithic application with some graphical user interface (GUI), then needed to use some functionailty via scripting or in some context where I don't have GUI elements and wrap it in a command-line interface (CLI).

What I typically end up with is a thing I call Library / CLI / GUI. The library has no dependencies on user interface elements, but offers all the functionality that is not specific to the CLI or GUI programs.

Example

Let's say that I need to read a CSV file from disk and invoke some REST calls based on that. I might structure that in three C# projects.

  1. ProcessorLib.csproj
  2. ProcessorUI.csproj
  3. ProcessorCmd.csproj

In this case, ProcessorLib might have a class that looks like this.

namespace MyProcessing {
  class Processor {
    public string CsvPath { get; set; }
    public Uri ServiceUri { get; set; }
    public void DoThing() {
      // Read CsvPath
      // Invoke some REST calls on ServiceUri.
    }
  }
}

This is all well and good, so now we can have ProcessUI be a simple wrapper on this.

namespace MyProcessing {
 // Possibly in a different namespace ...
 class ProcessorForm {
   public TextBox CsvPathBox;
   public TextBox ServiceUri;
   public Button ProcessButton;
   ...
   public void ProcessButton_Click(object sender, EventArgs args) {
     var p = new Processor()
     {
       CsvPath = CsvPath.Text,
       ServiceUri = new Uri(ServiceUri.Text),
     };
     p.DoThing();
   }
 }
}

The above is a very simplistic thing. The UI might choose different controls and handle things like prior history, interactive validation, maybe handle asynchrony and update on progress.

The CLI can also be quite simple.

namespace MyProcessing {
 class ProcessorCmd {
   public static void Main(string[] args) {
     // validate args ...
     var p = new Processor()
     {
       CsvPath = args[0],
       ServiceUri = new Uri(args[1]),
     };
     p.DoThing();
   }
 }
}

The CLI might take pains to make sure that there's proper logging to disk if it will run unatteneded, or that the exit code on error is something that can be interpreted by other tools.

Variations

You'll often see a ProcessTest project with unit tests for the common functionality, which typically has the most interesting code and should be easy to test in isolation.

In other languages where interoperability takes a fair bit of work and is independent, like projecting C++ into ActiveX, you might see something like a ProcessAX project that simply provides a script-friendly head to the core library.

Sometimes configuring things takes a lot of work, and configuration files and payloads can be defined in the common library. It's useful to have these be extensible so you can have UI-specific things if needed, and to be tolerant of unknown values so the extensions don't require updating everything at once.

.NET Idioms

There are some challenges that arise because the core library should avoid taking dependencies on user interface elements.

What should you do with long-running processes? In the past, having a component own a thread and report on completion was common; today, async programming is well supported.

You can have the UI disable controls as needed during the operation, and have the CLI simply Wait on the asynchronous task.

What about progress feedback for these operations? In earlier versions we would typically roll out events, but these days IProgress is common to see. Just have GUI and UI provide their callbacks if desired, then call into it from the asynchronous work.

What about data objects? I like to provide plain old data objects as much as possible, and then augment them with the following as needed, typically by reusing concepts from the System.ComponentModel namespace.

Remember, the pattern has guidelines not rules! It's meant to describe and guide, not mandate.

Happy coding!

Tags:  design

Home