Component
1 Introduction
1.1 Guide
1.2 Limitations
2 Reference
2.1 Components
gen:  component
component?
component-start
component-stop
2.2 Systems
system?
make-system
define-system
system-start
system-stop
system-ref
system-replace
system->dot
system->png
3 Acknowledgements
7.7

Component

Bogdan Popa <bogdan@defn.io>

 (require component) package: component-lib

1 Introduction

This library helps you manage the lifecycle of stateful objects in your application. It ensures that objects are started, linked together and stopped in the correct order.

By writing programs in this style, you trade some flexibility for clarity around how and when your objects are initialized and your code becomes easier to test since swapping out real implementations of components for stubs is trivial.

1.1 Guide

Let’s assume that you’re writing a web application that emails users when they sign up. Your components are probably going to be:

Assuming that each of the identified components is a struct that implements the gen:component interface, your system might look something like this:

(define-system prod
  [db make-database]
  [mailer make-mailer]
  [user-manager (db) (lambda (db) (make-user-manager db))]
  [http (db mailer user-manager) (lambda (db mailer user-manager)
                                   (make-http db mailer user-manager))])
 
(system-start prod-system)
(system-stop prod-system)

The system specification is made up of a list of component specifications. Each component specification is made up of the unique name of a component in the system, an optional list of dependencies (other component names) and a function that can be used to construct that component from its dependencies. There are no constraints on the names of the components in the system and you can have multiple components of the same type.

The define-system form builds the system struct and its internal dependency graph but does not start the system.

The call to system-start starts the db and the mailer first (one or the other may be first since neither has any dependencies), then the user-manager and finally the http server.

Finally, the call to system-stop stops all the components in the system in the reverse order that they were started in.

1.2 Limitations

Components that have no dependencies and no dependents are never started.

When a component fails during startup or shutdown (i.e. raises an exception), systems don’t attempt to perform any sort of cleanup. This isn’t really a limitation, but something to be aware of.

2 Reference

2.1 Components

Components are the basic building blocks of this library. Every component needs to know how to start and stop itself.

To define your own components, implement the gen:component interface and its component-start and component-stop functions. Here’s an minimal component implementation:

(struct mailer (started)
  #:methods gen:component
  [(define (component-start a-mailer)
     (struct-copy mailer a-mailer [started #t]))
 
   (define (component-stop a-mailer)
     (struct-copy mailer a-mailer [started #f]))])

The implementations of component-start and component-stop must follow the contracts defined below.

interface

gen:component

procedure

(component? component)  boolean?

  component : any/c
The generic interface that specifies components.

procedure

(component-start component)  component?

  component : component?
Starts a component.

procedure

(component-stop component)  component?

  component : component?
Stops a component.

2.2 Systems

Systems group components together according to a declarative specification.

When a system is started, its components are each started in dependency order (if a depends on b which depends on c then c is started first, then b then a) and injected into their dependents’ factory functions (c is passed to b which is finally passed to a).

When a system is stopped, its components are stopped in the reverse order that they were started in.

procedure

(system? system)  boolean?

  system : any/c
Returns #t if system is a system.

procedure

(make-system spec)  system?

  spec : 
(listof (or/c (list/c symbol? any/c)
              (list/c symbol? (listof symbol?) any/c)))
Creates a system object according to the given dependency specification, but does not start it. A user error is raised if the spec contains any circular dependencies between components.

syntax

(define-system name component ...+)

 
name = id
     
component = [component-name factory]
  | [component-name (dependency-name ...) factory]
     
dependency-name = component-name
     
component-name = id
     
factory = expr
Syntactic sugar for define and make-system. -system is appended to the given name so

(define-system prod
  [db make-db]
  [app (db) make-app])

defines a system called prod-system.

procedure

(system-start system)  void?

  system : system?
Starts a system.

procedure

(system-stop system)  void?

  system : system?
Stops a system.

procedure

(system-ref system name)  component?

  system : system?
  name : symbol?
Get a component by its name from a system. Raises exn:fail if called before the system is started or if name refers to a component that wasn’t defined.

procedure

(system-replace system id factory)  system?

  system : system?
  id : symbol?
  factory : any/c
Returns a new, stopped, system with the factory for the id component replaced by factory. This is useful if you have a large system and you want to replace one of its components with a stub (eg. for a web app’s end-to-end tests).

procedure

(system->dot system)  string?

  system : system?
Generate dot notation representing a system’s dependency graph.

procedure

(system->png system output-path)  boolean?

  system : system?
  output-path : path-string?
Generate a PNG of a system’s dependency graph. Requires Graphviz to be installed and its dot command to be on the PATH.

3 Acknowledgements

This library draws inspiration from Stuart Sierra’s "component" library for Clojure.