extreme late binding and loose coupling

2008-09-23 @ 21:32#

when building a scale-able web app it's important to keep any binding between data storage and ultimate output loosely coupled. i see lots of examples of applications that define a data object (Customer) along with a set of properties (Id, Name, Address, etc.) and then work up code that can 'auto-magically' serialize that object into some desired output format (XML, Atom, JSON, etc.). there are a number of problems with this approach.

first, any changes in the underlying object require a rebuild of the app. that means not just changing the data type of an element, but adding a new one; removing an old one; renaming it; turing it from a simple value to a collection, etc. granted some of these changes might require compiling anyway, but other might not. this is especially true if you want to support multiple versions of the app over time. it's not nice to force every client to accept the new object even if they just want to stick w/ trusty version 1.0 of your app.

second, developers loose control over the serialization of the object. it's all done 'under the covers.' and even when some frameworks allow for customizing the serialization, it's usually pretty limited. for example, i have yet to see any serializer turn an Address into valid (X)HTML.

third, even if you have a successful serializer library, what if some users need an element named 'Customer' and others need that same element with the name 'Item'? what if some don't want to see certain elements at all? you're stuck building new objects and creating pathways to support these various requests. more code. more breakage.

instead, it's better to create an environment where it's easy to accept requests and produce responses tuned exactly to customers (or your own) needs. i prefer an XML pipeline approach. in the framework i've been using for close to a year now, all requests are treated to a series of XML/XSL/XSD steps that finally results in real work and output.

for example, below is a generic GET implementation for data stored in an SQL database. note the steps: 1) transform the arguments; 2) validate the arguments; 3) transform the validated arguments into valid T-SQL command(s); transform the results of the T-SQL into a valid representation (based on the requested content-type). this pattern works for any data stored in relational database. it works for any set of incoming arguments. it works for any supported representation format. it just works.

// deliver from cache, if available
if (ch.CachedResourceIsValid((HTTPResource)this))
{
  return;
}

// transform argument list
xsl_file = (File.Exists(XslGetArgs) ? XslGetArgs : string.Empty);
if (File.Exists(xsl_file))
{
  xslt = new XslTransformer();
  out_text = xslt.ExecuteText(xmlin, xsl_file, ArgumentList);
  xmlin.LoadXml(out_text);
}
else
{
  xmlin.LoadXml("");
}

// validate args before continuing
xsd_file = string.Empty;
xsd_file = (XsdFileMtype != string.Empty ? XsdFileMtype : XsdFile);
if (File.Exists(xsd_file))
{
  SchemaValidator sv = new SchemaValidator();
  string sch_error = sv.Execute(xmlin, xsd_file);
  if (sch_error != string.Empty)
  {
    throw new HttpException(400, sch_error);
  }
}

// transform args into sql and execute
xsl_file = (File.Exists(xslGetRequestContentType) ? xslGetRequestContentType : string.Empty);
if (File.Exists(xsl_file))
{
  xslt = new XslTransformer();
  string cmdtext = xslt.ExecuteText(xmlin, xsl_file, ArgumentList);

  // execute sql and return results
  SqlXmlCommand cmd = new SqlXmlCommand(util.GetConfigSectionItem(Constants.cfg_exyusSettings, this.ConnectionString));
  cmd.CommandText = cmdtext;

  using (XmlReader rdr = cmd.ExecuteXmlReader())
  {
      xmlout.Load(rdr);
      rdr.Close();
  }

  // transform outputs into representation
  xsl_file = (File.Exists(xslGetResponseContentType) ? xslGetResponseContentType : string.Empty);
  if (File.Exists(xsl_file))
  {
      xslt = new XslTransformer();
      out_text = xslt.ExecuteText(xmlout, xsl_file, ArgumentList);
  }
  else
  {
      out_text = util.FixEncoding(xmlout.OuterXml);
  }

  // handle caching of this resource
  ch.CacheResource((HTTPResource)this,out_text);

  // return the results
  this.Response = out_text;
}
else
{
  throw new HttpException(500, "missing sql transform");
}

code