Difference between revisions of "Real World Applications/Event Driven Applications"

From HaskellWiki
Jump to navigation Jump to search
 
(One intermediate revision by the same user not shown)
Line 27: Line 27:
 
Here is a straightforward way to define Events in Haskell:
 
Here is a straightforward way to define Events in Haskell:
   
<haskell><code>
+
<haskell>
 
data Event =
 
data Event =
 
EventUserExit -- User wants to exit
 
EventUserExit -- User wants to exit
Line 35: Line 35:
 
| EventUserRedo -- User wants to redo
 
| EventUserRedo -- User wants to redo
 
deriving(Eq,Show)
 
deriving(Eq,Show)
</code></haskell>
+
</haskell>
   
 
Events can be high level or low level depending on how "low level" in the system you are operating.
 
Events can be high level or low level depending on how "low level" in the system you are operating.
Line 150: Line 150:
 
UI can be swapped without changing Domain.
 
UI can be swapped without changing Domain.
 
Try that with your average application.
 
Try that with your average application.
  +
  +
''dmUpdate'' is not generating any events.
  +
However in a more realistic application, updating the domain will typically result in additional events being generated.
   
 
Domain is ''pure''.
 
Domain is ''pure''.
 
All the goodness of functional programming.
 
All the goodness of functional programming.
  +
The idea is to keep as much of the code UI independent.
   
 
Domain can be tested without the UI.
 
Domain can be tested without the UI.

Latest revision as of 08:53, 4 July 2014

Introduction

An event driven application/architecture is an application/architecture that is driven by internal/external events. Most non-trivial applications/architectures (from Operating Systems to Web Servers and Enterprise applications) are event driven.

Examples of events are:

  • A Loan Application has been accepted/rejected (commercial business).
  • A new Rostering Schedule is ready for distribution to all crew (Airline Management System).
  • An Illegal Trade Pattern has been detected (Trading Fraud Detection System).
  • A simulated car has hits another simulated car (Commercial Racing Game).
  • A robot has reached its destination (Real Time Warehouse Management System).
  • A HTML message has been received (Web Server).
  • A key has been pressed (Text Editor).

The examples demonstrate that events can be anything from high level business events ("A loan application accepted/rejected") to low level events ("User pressed key").

In the following, I will show a way to architecture a Haskell system so that it can scale from small "toy" applications (dealing with low level IO events) to large scale, high volume, distributed, fault tolerant "Enterprise" scale applications.

Please note that the following is not the only way to attack the problem. So please contribute your (clearly superior of course) alternative way to do it here: Real World Applications

Events in Haskell

In the following, I define an "Event" to be a value describing something that has happened in the past. And yes this should really be called an "Event Notification" but life is too short :-)

Here is a straightforward way to define Events in Haskell:

data Event =
    EventUserExit            -- User wants to exit
  | EventUserSave            -- User wants to save
  | EventUserSaveAs String
  | EventUserUndo            -- User wants to undo
  | EventUserRedo            -- User wants to redo
  deriving(Eq,Show)

Events can be high level or low level depending on how "low level" in the system you are operating. Within a UI sub-system the events are typically low level (key pressed, window closed). In a large scale distributed system the events are typically high level business events (EventCustomerCreated <details>).

A Tiny Event Driven Haskell Application

Let's warm up with a tiny toy event driven Haskell application. In this case, an event driven calculator that can add numbers and exit. Not very exciting but it is useful for introducing a number of key concepts.

I will in the following use the term "Domain" to stand for the application state. Fell free to use "Shipping" instead of Domain if your application area is shipping or "Game" if your domain is a game application. More info here: Domain Model.

Here is the Domain for the calculator application:

module Main where

data Domain =
  Domain Int
  deriving(Eq,Show)

The calculator state simply keeps track of the current value.

The next step is to define the events that the user can generate:

data Event =
    EventAdd Int
  | EventExit
  deriving(Eq,Show)

In this case all the user can do is to add and exit.

With those two definition, we can now write the "domain update" function. It simply takes the current state of the domain and an event as input and outputs the updated domain:

dmUpdate :: Domain -> Event -> Domain

dmUpdate (Domain v) (EventAdd a) = Domain (v + a)

dmUpdate dm _ = dm

For this tiny toy example all we can do is to react to the EventAdd event and calculate the new value.

The next step is to write the UI. For now it doesn't really matter how the UI is implemented. All that matters is that it can somehow present the Domain to a user and get user events back (More on this later):

uiUpdate :: Domain -> IO [Event]

uiUpdate (Domain v) = do
  putStrLn $ "Value is now: " ++ show v
  if v < 3 then
    return [EventAdd 1]
  else
    return [EventExit]

For this tiny toy example the UI will not even read from stdin. It instead return EventAdd events until the Domain state value is >= 3. After which it generates the EventExit event. Yes it is just a tiny toy example :-)

Given dmUpdate and uiUpdate we can now write what is traditionally called the "Event Loop". The event loop is the beating heart of an event driven application:

run :: Domain -> [Event] -> IO ()

run dm [] = do
  events <- uiUpdate dm
  run dm events

run _ (EventExit:_) =
  return ()

run dm (e:es) =
  run (dmUpdate dm e) es

For this tiny toy example, the event loop simply asks the UI for new events. And updates the domain when events are available. It exits when EventExit is received from the UI.

And here is the main function to bootstrap the event loop:

main :: IO ()
main = run (Domain 0) []

main simply creates a new domain (Domain 0) and calls run with an empty event list to get started.

The result of running the application is:

Value is now: 0
Value is now: 1
Value is now: 2
Value is now: 3

Consequences

The tiny application above is simple. However a number of key choices have been made that will profoundly shape how the application scales from this tiny application to "Enterprise" level:

UI depends on Domain - Domain does not depend on UI. This is the complete opposite of how most applications are (wrongly) developed.

UI can be swapped without changing Domain. Try that with your average application.

dmUpdate is not generating any events. However in a more realistic application, updating the domain will typically result in additional events being generated.

Domain is pure. All the goodness of functional programming. The idea is to keep as much of the code UI independent.

Domain can be tested without the UI. No need to setup Database to test.

The Domain API is Value based not API based. Events can be queued, recorded, distributed and replayed.

Events can (should) be asynchronous.

And by the way, this is not MVC/MVP as will be shown later :-)

Growing the Application

So how do we grow this tiny application to "Enterprise" scale? Read on:

  • Simple File Storage
  • Logging
  • Testing
  • Crash Recovery
  • Undo/Redo
  • Time
  • UI
  • Performance Monitoring (Dashboard)
  • Automatic Server Scaling
  • Complex Event Processing
  • (Multiple) Databases (Separating Online and Reporting)
  • Client/Server (Haste etc.)
  • Humble "File in Directory" Events
  • Reporting
  • Business Workflow
  • Remove Control
  • Event Sourcing
  • Event Bus
  • Security (Event Pattern Based)
  • Interop with non-Haskell applications

Questions and feedback

If you have any questions or suggestions, feel free to mail me.