I have a system I've been working on this week where I'm having a hard time balancing separation of concerns with easy extensibility. I'm adding new types to the system, and it feels like shotgun surgery.
The basic idea is that data is collected (polled) from a remote system and then made available to a number of different kinds of clients. To support their interface type (protocol), I'm doing a lot of translation.
I'm going to try to simplify this so I can fit it in a question box.
There's a FruitService out in the world with an SNMP interface. My publisher's job is to collect that data and then publish it.
- So for each kind of new Fruit we decide to publish, I create a class that knows how to pull the attributes for that fruit via SNMP.
- Internally Fruit is translated to DTOs (the same objects used to talk to client #1 (RMI)).
- A cache is updated and checked for changes to trigger async notifications.
- A class to translate the fruit into the xml schema used by another client (#2).
- A class to translate the fruit into simple attribute/value pairs used by another client (#3).
In the end I create 5 classes and edit 8 more to plug in a new type. It's not a lot of work; couple hours to write code, test, and check-in for a simple type. Note that there isn't a lot of commonality in the fruit types (e.g.: pear and coconut), so most of the common abstractions are based around the process of updating the data and not the data itself.
My design concerns are:
- The SNMP interface changes 2-3 times a year
- The xml schema (client 2) changes 10 times a year.
- Adding new types happens less frequently, generally when someone gets around to it. But maybe if it was easier...
So the notional goal with the design was to handle those external changes easily. This lead to all the separation in the translation layers. But adding types feels harder than I'd like.
Is there a technique or example I might look into? An idea I'm missing? Am I applying SRP wrong and there should be an Apple type that speaks SNNP, xml schema, DTO, etc?
Client #1, the RMI client, is custom written when a new fruit is added, to take advantage of all the fruit's capabilities. So if we decide to pull banana information from the FruitService we will also write a client that allows you to peel the banana, get a notification when it's ripe, and tell you which bunch it belongs to - along with the basic fruit attributes like size, weight, color, time till rotten, etc.