Lifecycle Objects as Extension Points

lifecycle objects

Something I’ve found myself doing more and more is writing lifecycle objects that add notification-like extension points to other objects. A good example is the MessageLifecycle interface and its implementations in PMG’s queue library.

Rather than pollute the queue consumers with events or other more generic things directly, the lifecycle provides and extension point into various the various stages as a message moves through a queue consumer. It doesn’t allow modification of the process itself, but it does let clients of the consumer reach in and see what is going on.

Multiple lifecycles can be composed together easily enough.

This can end up being more code, but the result is a much more flexible and meaningful set of abstractions.

Last week I was writing some abstractions around streaming CSV files and needed to send a notification to the client object when each file began. I started with an interface like this:

interface FileReader
{
  public function read(array $files, callable $start) : iterable;
}

This worked okay, but in tests I found that the $start callback and its functionality were extremely hard to test. I also wasn’t particularly sure if we’d need additional notifications as the files moved through their lifecycles.

Rather than keep going down the path with a simple callback, I added a lifecycle object with a single starting method that took the filename as well as incoming columns.

interface FileLifecycle
{
  public function starting(string $filename, array $columns) : void;
}

And then added an implementation of that lifecycle that did the things originally handled by the $start callback.

The end result? I could actually test the behavior I needed to verify and ended up with a more flexible abstraction that can grow with the application over time.