Parsers are essential to the interface layers of an application. A message based system requires a socket listener that will convert the contents of the stream from the network into usable collection of objects. In an inversion of control container, these request scoped objects should be registered instances in the container in order to build/fetch more complex objects, potentially of longer lived scope. Parsed request scope objects should be of classes that enforce invariants, but often will be simple strings in language specific form; char * or std::string being the most common for C++.
Take the example of a userid passed in as part of the request. There really is no format that this string conforms to other than, perhaps, some constraints of the system. To create an immutable UserID class may really just force casts to char * or std::string when the UserID is to be used.
There are many objects, specifically request scoped objects, that need to be created based on the runtime values provided by the system. In a pure Inversion of COntrol (IofC) environment, the parser would create each, and then add them to the container. This may require a large amount of coding in a manner specific to the IofC container. One goal of IofC is to minimize the amount of work that is specific to the container.
Many API’s handle this by creating a map. In the Java HTTP Servlet API, the request object has two string to string maps, one for parameters and one for cookies. This may be a good middle ground between two APIs. A better approach is what Struts does using the Apache Jakarta PropertyUtils API. The Action field of a form specifies an URL that in turn starts with a java object of type Action. THe action is configured in an XML file (Ugh!) that specifies the subclass of ActionForm. The request parameters are bound to the properties of ActionForm using the Java Bean coding convention. Thus a parameter user.homePhone.areaCode =415 would force the reflection equivalent of:
action.getForm().getUser().getHome().setArea(“415”);
One problem with this implementation is that any exception thrown at any point of this binding would force halt to the entire setting of parameters. The net effect is lots of Struts specific coding. Personally, I am not a big fan of getter/setter dependency injection, as it tends to violate the precept that classes enforce invariants. It doesn’t have to, just that code written that way tends to be of the nature where an object can have an invalid state until the setter is called. However, the setter approach does work well with builders, if the set method can be called multiple times in the role of “BuildPart.”
When discussing the marshaling layer we can often think in terms of combing interpreters with builders. The interpreter is responsible from converting from the marshaled format to object format for simple data objects. Each of these objects is added to a larger complex object. In terms of the Builder pattern, the interpreter plays the role of the Director. Only once the entirety of the message is parsed will the director call GetResult. Exceptions thrown during the parsing of the message are collected up so that they can be reported in bulk.
One common objection to this style of programming is the cost of multiple exceptions thrown during the parsing stage. Reducing the number of exceptions thrown should be a performance tune performed after the system is operational. The general structure is to split the parse of a data object into a three stage process. First create builder that takes the object in string form. Second, ask the builder if the marshaled form is valid. Third, fetch the object from the builder. If stage two returns false, add an error to the collection of errors and short circuit the building process. Note the higher degree of complexity over performing the parsing in the constructor. The constructor has to validate the format it is given from the builder, or has to know about the builder object, creating a bidirectional dependency. The logic in calling the builder and the return code has to be coded by hand or has to fit into some larger framework.
The degenerate case also seems to be the most prevalent: confirm the format of the data objects, but then transfer them around as strings. The problem with this approach is that each layer of the application will be written not trusting the other layers, and the objects will be re-parsed, potentially following different rules. From the increase in code size, complexity, and potential for error, we can infer that we should avoid this approach.
Since the builder can be created with minimal dependencies, and most of these can be defined statically, it should be a request scoped component defined in the IofC container. We have to decide at what point in the parsing process do we switch from a general purpose parser to a specific class dedicated to the data coming off the socket. Ideally, the message format provides a way to specify the message type and version early in the stream. This can be the key used to fetch the specific parser. The stream coming off the socket should conform to one of the standard types of the language. In C++ this means something out of std::. The parser can depend on the builder. After the parsing process, one last call into the IofC container kicks off the business processing, that which takes the parsed message and does something with it. At this point we can use something as simple as a functor: Everything is in the IofC layer.
There is a lot here subject to interpretation. The stream may or may not be encoded according to a public protocol, a language specific feature, XML, UUEncoded, encrypted, compressed, and so on. A pipeline can handle the transformation. Converting from stream to objects may use this pipeline, or be a portion of the pipeline, converting from a character or binary based stream to a stream of objects. The issue here is always how to get the objects available to the container without too much container specific logic. If the value comes in key value pairs, the key needs to provide enough information to determine which instance of which class to create.
Since your inversion of control layer should be minimally invasive, you don’t want to have each call in the building of the input validation require knowledge of this layer. Something about the network protocol is going to be unique enough to pull in the processor for your code. This is going to be the URL, the message type, or something unique to your protocol. The should be enough to select the interpreter and the builder objects, connect them, and let the parser become the pump from there on out. The builder should registered in the Inversion of COntrol container. When something needs the end product of the build process, it should get this from the container, which will delegate to the builder. This should strike the right balance between type safety and flexibility.