6

Upon my assignment to a project, I discovered that many message classes were received and then kept intact and passed around inside the receiving application. When these messages changed, code throughout the entire application had to be changed. Since this problem, I have gotten into the habit of converting messages to a new format upon reception.

However, it feels like I am directly violating DRY (don't repeat yourself): I have these near-identical internal classes that I create from external messages.

Where is the line between eliminating dependencies and eliminating redundancy? Should each module have their own data formats?

2
  • An awfully general question, but, let's start with why data classes are being passed around, instead of objects with methods? It seems like a violation of Tell, Don't Ask. Are they crossing some boundary that doesn't let you pass real objects? Why not create real objects on the other side? Or is that what you are introducing?
    – psr
    Commented Nov 8, 2011 at 21:13
  • Sounds like a job for interfaces.
    – CaffGeek
    Commented Nov 8, 2011 at 21:49

3 Answers 3

7

I'm assuming that an "external message" is something that's coming over a network protocol, e.g. RPC, SOAP, Protocol Buffers, etc.

In that case, it's absolutely a good thing to maintain two separate models. One for messaging, one for domain. It's not unusual to have yet another model for presentation and another model for data.

It seems awfully repetitive, but there are plenty of automapping tools that can reduce the effort to near-nil; in .NET we have AutoMapper, in Java there's Dozer, etc.

More importantly, the alternative is an obnoxious, bloated canonical model which forces everything that touches it to deal with all of the bizarre idiosyncracies of everything else that touches it. When you need to map between models, you usually have the ability to do versioning (choose sane defaults for new fields), but when you have a single shared model... forget about recompiling just one application, you have to rebuild and redeploy your entire architecture. This sucks when you have a distributed system.

Just because two classes have very similar structures does not mean one of them is redundant:

  • Domain models are "thick" models, meant to accurately model the real world and contain all or most of the business logic for your application. Encapsulation is king and domain objects emphasize behaviour over data.

  • Messaging models are by definition "thin" models containing nothing but data; in many cases a message has already been validated (in the case of an event message) or will have to be re-validated anyway (in the case of a command), so validation tends to be limited to the format of the message. In addition, the structure of one of these message models is almost always optimized for transmission efficiency (no unnecessary fields) and there may be several "metadata" attributes (like security headers) that would make no sense at all in any other model.

  • Data models are (traditionally) intended to provide a bridge to a DAL or ORM. Modern tools have gotten pretty good at aligning these with domain models but not nearly all of the way there. Generally there are a lot of annoying restrictions on a data model that also make no sense in a domain model, such as ID properties on every class, bidirectional references, setters on every property, and in some cases every member has to be virtual. The last two are especially problematic; most often you want your domain models to be immutable and sealed (not inheritable).

  • And finally the presentation model is likely to be much flatter than the domain model, have all sorts of formatting code, and mostly string properties (which have all manner of validation attached to them). Which fields are read-only will depend on the features that the UI is supposed to provide.

It's really, really hard, bordering on impossible, to keep all of these conflicting requirements in sync when trying to maintain an architecture-wide or even application-wide shared model.

Ultimately you will end up doing less work by having custom models with very few dependencies, as opposed to one model with a very high number of dependencies.

1
  • +1 Just a fantastic answer that matches lots of painful experience. I've tried the "one model to rule them all" and it failed miserably every time. It works great, until the system grows to a certain point, and it goes downhill very fast. Focus on reducing the friction of mapping between the models rather than reducing the number of models in play. Commented Nov 8, 2011 at 21:57
1

To avoid violating the DRY principle you need to figure out the one place information about the messages goes. You also don't want dependencies from other modules.

How about putting the information in the message itself? Not just the data, but how to use the data. That way all the code that uses the message can still use it, but changes to the message don't have to propagate beyond it.

Unless of course changes are to the external interface of the message. But in that case it's difficult to say how to limit the ripple effect, especially without more specific information. But the question indicates it's a format issue, which should be taken care of by making the message a "real" object.

0

I would say this is a break between information and data. Consider this:

Your client has a choice of files they can send you. Either a csv file full of rows with username,id,address and so on, or a pdf with the same stuff on a form. Which one do you want to read? Which one do you want to have a program use?

Data is the fundamental values you are talking about, ID=1 and USERNAME=SRR. Information is data and context. Put the username here, drop the id. Your message classes should be data repositories, holding all the information anyone may require and having methods to collect the raw data.

Then, you pass pointers to these objects around to the various different things that need access to the data. One of the methods will be a simple wrapper around the method calls necessary to produce the data you need, then combine it with the current context to get the information desired.

However, data does require a method of collecting it, and this requires a clearly defined interface. Then you are into interface design, with the requirements therein. If new data may need to be added later on, then the interface needs to account for that. Perhaps your get method has a parameter that lists the various items desired, or a get method is added for each data variable. Whatever you pick must scale, or you are destined to go in and rebuild all the different pieces each time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.