I’ve recently disputed with my colleague about the following situation:
I’ve developed a framework that processes the resources (in java, although its not really important). It goes (simplified, pseudo-code) like this:
class Algorithm {
Processor processor = ...;
execute() {
forEach(Resource r : readResourceNamesFromFileSystem()) {
DomainObject do = convertResourceToDomainObject(r);
processor.process(do);
}
}
}
interface Processor {
process(DomainObject do);
}
Class Algorithm is an entry point to my framework. Resource is a representation of some content in the filesystem (it has methods for reading the data from file and the file name, which is important)
Class DomainObject (declaration is omitted) is a java representation of the content of the file depicted by Resource. DomainObject only has data and doesn’t have a filename inside.
Class Processor represents the business logic layer, the DomainObject can be processed, stored in Database and so forth.
The content data is supplied by others, we just know that these files contain the data that can be converted into DomainObject(s) which is a real object we work with in the framework. We store one DomainObject in one file.
Now we argue about the file name. Colleague of mine wants the file name to be propagated into the Processor and potentially to all the layers because its easy to print the logging message/throw an exception that contains a file name if something goes wrong during the processing, or maybe to write the message like “domain object stored in file (here__comes__filename) has been processed successfully”.
I see the point of my colleague (convenience) but I’m sure it breaks the design and encapsulation because the processor module shouldn’t really be aware of the “origin” of the DomainObject (today we store them in files one DomainObject per file, tomorrow we’ll maybe use something else, something that doesn’t necessarily has a filename).
I claim that we better catch the exception in the Algorithm.execute method and rethrow the exception with a filename/ log the filename. The log file will be less readable but the encapsulation won’t be broken.
In my understanding its a design vs clearness. Could you please share your opinion, who is right, who is wrong? Should I sacrifice the design for the clearness of logs?
BTW I’ve promised my colleague to ask this question so we’ll read the possible answers together 🙂
Thanks a lot in advance and have a nice day
1
Catch the exception at the level where you do have the file name, incorporate the file name into the error message, and rethrow.
This is a classic example of why exceptions are designed the way they are; they propagate up the call stack to the next available catch
clause, where new information can be added to them where it is available. This avoids the need to pollute your classes by propagating logging information throughout your class hierarchy.
Java will even maintain the original stack trace.
The whole point of having things like stream
objects is to abstract away details like file names. A stream
doesn’t care where the data it is streaming comes from, and some data sources don’t even have file names associated with them.
8
If you’re using a logging framework like Log4j, you can use a nested diagnostic context (NDC) to store the file name at the point it is available, then retrieve from the NDC at the time you log the messages.
Logj4 NDC
This can be even more useful because you can actually write a log4j appender that handles log messages specially based on the NDC content.
The specific case I used this was an embedded Jetty web server. Parts of the incoming URI identified the customer e.g.
http://domain/app/<customer-name>/otherstuff...
The Jetty request dispatcher extracted the value of “customer-name” from the URI and stored it in the Log4j NDC.
It was almost trivial to write a log4j appender that, when the NDC contained a customer name value, appended the message to a .log file.
That way we had a single coherent log file that contained all the diagnostic information in sequence, plus we had logs of activity relevant (and private) to each customer.