CS 346 (W23)
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Design

Architecture

Event-Driven Architecture

Applications often handle multiple types of processing: asynchronous, such as when a user types a few keystrokes, or synchronous, such as when we want a computation to run non-stop to completion.

User interfaces are designed around the idea of using events or messages as a mechanism for components to indicate state changes to other interested entities. This works well, due to the asynchronous nature of user-driven interaction, where there can be relatively long delays between inputs (i.e. humans type slowly compared to the rate at which a computer can process the keystrokes).

This type of system, designed around the production, transmission and consumption of events between loosely-coupled components, is called an Event-Driven Architecture. It’s the foundation to most user-interface centric applications (desktop, mobile), which common use messages to signal a user’s interaction with a viewable component in the interface.

What’s an event? An event is any significant occurrence or change in state for system hardware or software.

The source of an event can be from internal or external inputs. Events can generate from a user, like a mouse click or keystroke, an external source, such as a sensor output, or come from the system, like loading a program.

How does event-driven architecture work? Event-driven architecture is made up of event producers and event consumers. An event producer detects or senses the conditions that indicate thaat something has happened, and creates an event.

The event is transmitted from the event producer to the event consumers through event channels, where an event processing platform processes the event asynchronously. Event consumers need to be informed when an event has occurred, and can choose to act on it.

Events be generated from user actions, like a mouse click or keystroke, an external source, such as a sensor output, or come from the system, like loading a program.

An event driven system typically runs an event loop, that keeps waiting for these events. The process is illustrated in the diagram below:

  1. An EventEmitter generates an event.
  2. The event is placed in an event queue.
  3. An event loop peels off events from the queue and dispatches them to event handlers (functions which have been registered to receive this specific type of event).
  4. The event handlers receive and process the event.

An event loop which dispatches events in an event-driven architecture (https://www.tutorialspoint.com)

To handle event driven architectures, we often subdivide application responsibility into separate components.

Design

MVC

The most basic structure is Model-View-Controller (MVC), which leverages the Observer design pattern to separate business logic from the user interface.

Observer pattern, where observers monitor the subject for changes

MVC divides any application into three distinct parts:

  • Model: the core component of the application that handles state (“business logic layer”).
  • View: a representation of the application state, often as a user-interface (“presentation layer”)
  • Controller: a component that accepts input, interprets user actions and converts to commands for the model or view.

Similar to the observer pattern, the views monitor the system state, represented by the model. When the state changes, the views are notified and they update their data to reflect these changes. Notifications frequently happen through events generated by, and managed by, the toolkit that you’re using.

Basic MVC

Often this is realized as separate classes for each of these components, with an additional main class to bind everything together.

// main class
class Main {
  val model = Model()
  val controller = Controller(model)
  val view = View(controller, model)
	model.addView(model) 
}

We use an interface to represent the views, which provides the flexibility to allow many different types of output for the program. Any class can be a view as long as it supports the appropriate method to allow notifications from the model.

interface IView {
  fun update() 
}

class View(val controller: Controller, val model: Model): IView {
  override fun update() {
    // fetch data from model
  }
}

The model maintains a list of all views, and notifies them with state changes (indicating that they may wish to refresh their data, or respond to the state change in some way).

class Model {
  val views = listOf()
  fun addView(view: IView) {
    views.add(view)
  }
  fun update() {
    for (view : views) {
      view.update()
    }
  }
}

The controller just passes input from the user to the model.

class Controller(val model: Model) {
  fun handle(event: Event) {
    // pass event data to model
  }
}

One issue with this version of MVC is that the controller often serves little purpose, except to pass along events that are captured by the View (the View contains the user-interface and widgets, and generates events as the user interacts with it).

MVC remains common for simple applications, but tends to be implemented as just a model and one or more views, with the controller code included in the view itself.

MVP

Model-View-Presenter (MVP) keeps the key concept in MVC - separating the business logic from the presentation - and introduces an intermediate Presenter which handles converting the model’s data into a useful format for the views. This is typically done explicitly by the Presenter class. MVP arose from Taligent in the 1990s, but was popularized by Martin Fowler around 2006.

MVP with a presenter component between model and view

There have been multiple variants of MVP. We’ll focus on MVVM, probably the most popular variant.

MVVM

Model-View-ViewModel was invented by Ken Cooper and Ted Peters to simplify event-driven programming of user interfaces in C#/.NET. It’s similar to MVP, but includes the notion of binding variables to widgets within the framework, so that changes in widget state are are automatically propogated from the view to other components.

MVVM eliminates the controller and add viewmodels

MVVM includes the following components:

  • Model: as MVC, the core component that handles state. It can also map to a data access layer or database directly.
  • View: a representation of the application state, presented to the user.
  • ViewModel: a model that specifically interprets the underlying Model state for the particular view to which it is associated. Typically we rely on binding to map variables in the ViewModel directly to widgets in the View, so that updating one directly updated the other.

MVVM is much more common in modern languages and toolkits and has the advantage of replacing all “mapping” code with direct binding of variables and widgets by the toolkit. This greatly simplifies interface development.