Pioneer Plot: Cleansed Domain Layer

The last Pioneer Plot posed a dilemma: a Factory class for Legacy Objects takes in an Entity, which is polymorphic, and spits out an equivalent Legacy Object. You want all references in your domain layer to be self-contained so that it focuses on domain logic. What do you do?

My answer

Since polymorphism is involved, the first instinct is to double dispatch:

interface Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory);
}

class OneEntity implements Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory)
	{
		return factory.newLegacyObjectFrom(this);
	}
}

class AnotherEntity implements Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory)
	{
		return factory.newLegacyObjectFrom(this);
	}
}

class LegacyFactory
{
	LegacyObject newLegacyObjectFrom(Entity e)
	{
		return e.asLegacyObjectWith(this);
	}

	LegacyObject newLegacyObjectFrom(OneEntity e)
	{
		... logic here ...
	}

	LegacyObject newLegacyObjectFrom(AnotherEntity e)
	{
		... logic here ...
	}
}

Pretty standard. But there are issues. First, the Entity interface contains a non-domain reference to LegacyObject. Second, LegacyFactory is in an anti-corruption layer, which uses the domain layer, and now the domain layer has a reference into the anti-corruption layer, causing a circular dependency. Crap.

Option 1

Option 1 is…forget the anti-corruption layer and put it all into the domain layer. I know, I laughed too.

Option 2

Option 2 is to bite it and use instanceof.

class LegacyFactory
{
	LegacyObject newLegacyObjectFrom(Entity e)
	{
		if(e instanceof OneEntity)
		{
			OneEntity entity = (OneEntity)e;
			... logic here ...
		}

		if(e instanceof AnotherEntity)
		{
			AnotherEntity entity = (AnotherEntity)e;
			... logic here ...
		}

		throw new RuntimeException();
	}
}

The smells here should be obvious, but the advantage is that the Entity interface no longer refers to the anti-corruption layer and there is no circular dependency.

Option 3

Option 3 is to change your architecture to include an intermediate package that both the domain layer and the anti-corruption layer use. This way, all references flow downward. You know, invert the dependency.

// domain layer
interface Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory);
}
// domain layer
class OneEntity implements Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory)
	{
		return factory.newLegacyObjectFrom(this);
	}
}
// domain layer
class AnotherEntity implements Entity
{
	LegacyObject asLegacyObjectWith(LegacyFactory factory)
	{
		return factory.newLegacyObjectFrom(this);
	}
}
// anti-corruption layer
class LegacyFactoryImpl implements LegacyFactory
{
	LegacyObject newLegacyObjectFrom(Entity e)
	{
		return e.asLegacyObjectWith(this);
	}

	LegacyObject newLegacyObjectFrom(OneEntity e)
	{
		... logic here ...
	}

	LegacyObject newLegacyObjectFrom(AnotherEntity e)
	{
		... logic here ...
	}
}
// intermediate layer
interface LegacyFactory
{
	LegacyObject newLegacyObjectFrom(Entity e);

	LegacyObject newLegacyObjectFrom(OneEntity e);

	LegacyObject newLegacyObjectFrom(AnotherEntity e);
}

However, if you try to actually do this, you’ll find it to be impossible with the double dispatch constraint. There is still a circular dependency between the domain layer and intermediate layer. If you decide to merge these two, then you have the domain layer with a reference to LegacyObject. The best you can do is fudge it:

That intermediate layer becomes a special part of the domain layer that is allowed to hold “impure” (non-domain) references. I find this to be quite artificial.

So?

I actually don’t have a good answer here. Each option has philosophical questions to answer in lieu of its acceptance. Choosing Option 2 means you accept that some tight coupling is acceptable in a factory, namely coupling to a class hierarchy. Choosing Option 3 means you place a high premium on referential integrity and are OK with sub-packages referencing their parents. Choosing Option 1 means you think Option 3 is not worth the effort, but that you also have the discipline to not let too many “impure” references to leak into the domain layer.


COME my tan-faced children,
Follow well in order, get your weapons ready,
Have you your pistols? have you your sharp-edged axes?
Pioneers! O pioneers!

For we cannot tarry here,
We must march my darlings, we must bear the brunt of danger,
We the youthful sinewy races, all the rest on us depend,
Pioneers! O pioneers!

Announcer: You’re reading the EIP web-ring.

Leave a Reply