Udostępnij za pośrednictwem


Simulators or not?

I’ve been spending some time playing with Cockburn’s hexagonal architecture  (aka “ports and adapters”), and the extension I learned from Arlo, simulators. I’ve found it to be quite useful.

I was writing some code, and I ended up at a place I didn’t expect. Here’s the situation. I have the following external class (ie “port”).

class EntityLoader
{
    public EntityLoader(string connectionInformation) {}

    public IEnumerable<Entity> Fetch(EntithyType itemType) { … }
}

I need to use this class to some different kinds of entities, do some light massaging of data, and then query against the data. I’ll start figuring out what the adapter should be, and I’ll define it by the question that I want to ask it:

interface IPeopleStore
{
    IEnumerable<Employee> GetAllEmployeesForManager(Employee employee);
}

Now that I have the interface, I can use TDD to write a simulator that implements the interface:

class PeopleStoreSimulator: IPeopleStore
{
    public IEnumerable<Employee> GetAllEmployeesForManager(Employee employee) { ...}
}

The implementation for this will be pretty simple; I just add a way to get the list of employees for a manager into the simulator. Now I have unblocked my team members; they can code against the interface and use the simulator for their testing while I figure out how to write the version that talks to the EntityLoader.

And this is where it got interesting…

One of the cool things about port/simulator/adapter is that you can write one set of tests and run them against all of the adapters, including the simulator. This verifies that the simulator and the real adapter have the same behavior.

That’s going to be problematic because the interface for Entity doesn’t give me any way to put data into it, so I can’t use the simulator tests on it. It will also do two things; fetch the data from the entity and implement the GetAllEmployeesForManager() method, and because I can’t put data into it, I don’t have a way to write a test for the method().

It also violates one of my guidelines, which is to separate the fetching of data and the processing of data whenever possible. The problem is that we have the lookup method logic in a class that we can’t test – ie so we can’t adapt the data into what we need. That’s a good sign that adapter may not be a good choice here. How about a simpler approach, such as wrapper?

Let’s start with the lookup logic. We’ll make PeopleStore a simple container class, and that will make the lookup logic trivial to test.

class PeopleStore
{
    IList<Employee> m_employees;
    IList<Employee> m_managers;

    public PeopleStore(IList<Employee> employees, IList<Employee> managers)
    {
        m_employees = employees;
        m_managers = managers;
    }
   
    public IEnumerable<Employee> GetAllEmployeesForManager(Employee employee)
    {
        …
    }
}

Now, I’ll work on the wrapper level. After going with an interface, I end up switching to an abstract class, because there is a lot of shared code.

abstract class EntityStoreBase
{
    protected IEnumerable<Employee> m_employees;
    protected IEnumerable<Employee> m_managers;

    IEnumerable<Employee> FetchEmployees() { return m_employees; }
    IEnumerable<Employee> FetchManagers() { return m_managers; }
}

class EntityStoreSimulator: EntityStoreBase
{
    public EntityStoreSimulator(IEnumerable<Employee> employees, IEnumerable<Employee> managers)
    {
        m_employees = employees;
        m_managers = managers;
    }
}

class EntityStore : EntityStoreBase
{
    public EntityStore(string connectionInformation)
    {
        EntityLoader loader = new EntityLoader(connectionInformation);

        m_employees = loader.Fetch(EntityType.Employee)
                            .Select(entity => new Employee(entity));
        m_managers = loader.Fetch(EntityType.Manager)
                            .Select(entity => new Employee(entity));
    }
}

That seems to work fine. Now I need a way to create the PeopleStore appropriately. How about a factory class?

public static class EntityStoreFactory
{
    public static EntityStoreBase Create(IEnumerable<Employee> employees, IEnumerable<Employee> managers)
    {
        return new EntityStoreSimulator(employees, managers);
    }

    public static EntityStoreBase Create(string connectionInformation)
    {
        return new EntityStore(connectionInformation);
    }
}

This feels close; it’s easy to create the right accessor and the EntityLoader class is fully encapsulated from the rest of the system. But looking through the code, I’m using 4 classes just for the entity-side part, and the code there is either not testable (the code to fetch the employees from the EntityLoader), or trivial. Is there a simpler solution? I think so…

public static class PeopleStoreFactory
{
    public static PeopleStore Create(IEnumerable<Employee> employees, IEnumerable<Employee> managers)
    {
        return new PeopleStore(employees, managers);
    }

    public static PeopleStore Create(string connectionInformation)
    {
        EntityLoader loader = new EntityLoader(connectionInformation);

        var employees = loader.Fetch(EntityType.Employee)
                            .Select(entity => new Employee(entity));
        var managers = loader.Fetch(EntityType.Manager)
                            .Select(entity => new Employee(entity));

        return Create(employees, managers);
    }
}

This is where I ended up, and I think it’s a good place to be. I have a class that is well-adapted to what the program needs (the PeopleStore), and very simple ways to create it (PeopleStoreFactory).

Thinking at the meta level, I think the issue with the initial design was the read-only nature of the EntityStore; that’s what made the additional code untestable. So, as fond as I am of port/adapter/simulator, there are situations where a simple factory method is a better choice.