Adding Real Capabilities to Systems Through Naming



I believe that we have made a mistake in designing all objects as actors which perform a technical function instead of the concept that the objects represent.

Reflecting on Class Naming

When we create a class, we give it a name. What does this name represent?

Classes are boundaries. On the outside is the rest of the entire world. On the inside is some combination of data and behavior.

What goes inside a class and what stays outside? In order to determine what is contained within a class, we reference its design paradigm. The class might contain a long procedural ‘transaction list’ or it might contain a single unit of functionality designed to be composed into an aggregate behavior.

We might name the class after the procedure within, or maybe its role in another behavior, or maybe even the type of construct it is, I’m looking at you “RandomThingyInterface".

The Helper / Service Paradigm

If we’re modeling objects out of real-world domain concepts, and the class contains behavior to get a delivery id from the uber eats api then we might call it ‘delivery service’ because it’s a service that has behavior related to a delivery.

A service like ‘Delivery Service’ is a typical approach to implementing algorithms with side-effects. If the algorithm interacts with customer invoices, then it’s an InvoiceService (or InvoiceHelper). These are monolithic classes that contain many behaviors, have many methods, and often have many dependencies.

Designing helpers is thinking about some entity (in this case, a delivery) and creating functions that relate to this entity. This is often done when ORMs are involved. ORMs are smart data, in that they are essentially just a walking database record.

There’s not a good way to inject behaviors into an ORM model, it’s easier to implement use cases from the outside. We can create objects that mediate interactions between the ORM models and other objects in order to implement a use case. I believe this to be the most common origin of the Helper behavioral implementation paradigm.


$deliveryService = new DeliveryService(...);
$deliveryService->getDeliveryIdsForPerson($personId);

$deliveryService->markDeliveryAsComplete($ormDeliveryModel);

Object Actors

Object actors are animated objects. We create objects for a use case and then imbue them with life. We imagine the instantiated object and think about its rich world of experiences. What does it see? What does it do? How do we see these animated beings shuffling around a system, making themselves useful? We see them as entities that perform behaviors.

I am a person who programs, therefore I am a programmer. I am a person who fixes roofs, therefore I am a roofer. I am an object that dispatches events, therefore I am an EventDispatcher.

And thus, the DeliveryService or DeliveryHelper becomes an actor.

$deliveryIdFinder = new DeliveryIdFinder(...);
$deliveryIdFinder->findByPersonId($personId);

The ‘delivery finder’ does what? It finds delivery ids. What is the real world equivalent of a delivery id finder? There isn’t one. It does not exist.

Capabilities

What if we take the actor concept, and accept the fact that both:

  • the DeliveryIdFinder is a thing that doesn’t exist in the real world
  • we DO still need to get the delivery id

How can we model this behavior without imagining our objects as actors?

Let’s talk about modeling behaviors as capabilities.

While a delivery id finder doesn’t exist in the real world, the capability to find a delivery id does.

$findDeliveryId = new FindDeliveryId(...);
$findDeliveryId->byPersonId($personId);

Modeling this behavior as a capability can help us to implementing this behavior in the shape of our real-world domain solution space.

Scoping Capabilities

What effect does naming a class around the capabilities that it provides have on its environment?

class Example
{
    private DispatchEvents $dispatchEvents;
    
    public function __construct(
        DispatchEvents $dispatchEvents
    ) {
        $this->dispatchEvents = $dispatchEvents;
    }
    
    public function do(): void
    {
        $this->dispatchEvents->dispatch(
            ... events ...
        );
    }
}

A command whose variable name and method read like the following is not very interesting..

$this->dispatchEvents->dispatch()

So let’s talk about scoping object models.

Let’s take into account the fact that ‘class names’ and ‘variable names’ have different natures. The convention is generally to name the variable a camel case version of the class name (pascal case). Is it just a convention or is it something stronger?

Consider using the class name to define the capabilities that it brings to the scope. Consider the variable name to empower the expressiveness within the implementation of a behavior.

class Example
{
    private DispatchEvents $events;
    
    public function __construct(
        DispatchEvents $events
    ) {
        $this->events = $events;
    }
    
    public function do(): void
    {
        $this->events->dispatch(
            ... events ...
        );
    }
}

The variable name can exist to turn the capability into an elegant readable expression of the behavior that improves maintainability and expresses its intent along with its behavior.

The paradigm is |Capability| : |Readable Implementation|.

In the previous example, what use to be the ‘EventDispatcher’ now is modeled as the capability to ‘DispatchEvents’. In order to support the expressiveness of the behavior’s implementation, we name it ‘events’.

In this example, we’re completing a delivery. To do so we’ll need the delivery id.

class CompleteDelivery
{
    private FindDeliveryId $findDeliveryId;
    
    public function __construct(
        FindDeliveryId $findDeliveryId
    ) {
        $this->findDeliveryId = $findDeliveryId;
    }
    
    public function forPerson(PersonId $personId): void
    {
        $this->completeDelivery(
            $this->findDeliveryId->forPerson($personId)
        );
    }
    
    private function completeDelivery(DeliveryId $deliveryId): void
    {
        ...
    }
}

Name the variable based on its context. It can be used to communicate intent within the implementation that uses the capability.

Both class name and variable name are used for human consumption, but in two different ways.

capabilities exist in the real world. delivery id finders do not.

Why it Matters

When we look at stack-traces or diagrams of systems that were designed using actor objects, we see a lot of: event dispatcher, delivery id finder, fee confirmer, event listener, export builder, activity logger.

Would you prefer looking at these? dispatch events, find delivery id, confirm fees, listens to events, export a spreadsheet, log activity

By looking at a class constructor you can see what capabilities the class depends upon. Think about the ramification, classes start with a listing of the system’s capabilities that they depend upon.

The Vestige of Implementation

Capabilities are real.

The ability to ‘Dispatch Events’ exists. ‘FindDeliveryId’ is a thing that you need to do, but nowhere in our world is there a ‘delivery id finder’. (i think)

If the goal is to model these objects as animated units that perform behaviors, then ‘DeliveryIdFinder’ expresses that.

Theme Selection: