Designing Generic Architectures Using Templates

Many companies already have some established architectural design patterns which are supposed to be used in most of their applications. For example it makes sense to standardize the layering of business components. It also makes sense to establish specific rules how one business component can access another one. In the upcoming 9.4 release of Sonargraph-Architect we implemented a new feature in our architecture DSL which should make it very easy to add generic architectural blueprints to a quality model which would allow automatic verification of those architectural design patterns on any business component without having to create a component specific architecture.

For generic architectures to work properly it is a good idea to think about code organization, in particular the efficient use of name spaces or packages to reflect architectural intent. That can be easily done by using naming conventions:

com.hello2morrow.{component-name}.{layer-name}

In this simple example we assume that the component name is always the third part of a package/namespace name. The fourth part represents the layer. Knowing that we can now create a generic architecture description for this example:

// aspect file layering.arc
strict exposed artifact Service
{
    include "**/service/**"
}
 
strict exposed artifact Controller
{
    include "**/controller/**"
}
 
require "JDBC"
 
exposed artifact DataAccess
{
    include "**/data/**"
    connect to JDBC
}
 
public exposed artifact Model
{
    include "**/model/**"
}
 
public exposed optional artifact Util
{
    include "**/util/**"
}
 
deprecated hidden artifact Leftovers
{
    include "**"
}
 
// main file components.arc
template Components
{
    include "**/com/hello2morrow/(*)/**"
    exclude "**/com/hello2morrow/framework/**"
 
    artifact capitalize($1)+"Component"
    {
        apply "layering"
    }
}
 
public artifact Framework
{
    include "**/com/hello2morrow/framework/**"    
}

In the aspect file “layering.arc”¬†we define our standardized layering. At this point the layer artifacts do not really need to be exposed. That will be needed later when we add connection schemes to our example.

In the main file we use the new template feature. An template is a special kind of artifact that can dynamically create children artifacts out of elements that are matched by the pattern. The pattern must include at least on pair of parentheses so that we can extract the component name and use it as part of the name of a generated artifact. Inside of a template there always is a prototype artifact that uses a string typed expression as its name. ‘$1″ represents the first extracted name part from the matched architecture filter name. We append “Component” to the capitalized extracted name part to form the name of a generated artifact. We explicitly exclude classes of a framework that is mapped to an extra artifact that has been declared to be public so that everything defined in “Components” can use it.

For our example we assume there are 3 components distributed over the following 3 packages:

com.hello2morrow.order
com.hello2morrow.customer
com.hello2morrow.product

Then the template artifact “Components” would generate 3 children artifacts named “OrderComponent”, “CustomerComponent” and “ProductComponent”. All of those would have access to “Framework” because it is a public artifact defined beneath “Components”. But on the other hand the three generated components would not be allowed to access each other. Using templates there are currently three ways to regulate dependencies between generated artifacts:

  • No dependency allowed (like in the above example)
  • By marking the prototype artifact as “unrestricted” the generated artifacts could use each other (from default connector to default interface). It is always possible to restrict the default interface and/or the default connector by defining them explicitly.
  • By using connection schemes in combination with artifact classes. That approach will be explained later.

Using unrestricted generated artifacts

In the next example we use “unrestricted” in combination with a redefined default interface:

// main file components.arc
template Components
{
    include "**/com/hello2morrow/(*)/**"
    exclude "**/com/hello2morrow/framework/**"
 
    unrestricted artifact capitalize($1)+"Component"
    {
        apply "layering"
 
        interface default
        {
            export Service, Model, Util
        }
    }
}
 
public artifact Framework
{
    include "**/com/hello2morrow/framework/**"    
}

Now the 3 generated artifacts can call each other, but only the “Service”, “Model” and “Util” layers are exposed. If one of those generated artifacts were to access the “DataAccess” layer of another one this would be marked as an architecture violation.

Using connection schemes to regulate accessibility

If you need more control about the way generated artifacts can interact with other generated artifacts we need to use connection schemes in combination with artifact classes.

// main file components.arc
class Layered
{
    interface Service, Controller, DataAccess, Model, Util
    connector Service, Controller, DataAccess, Model, Util
}
 
connection-scheme C2C : Layered to Layered
{
    connect Service to target.Service, target.Controller, target.Model, target.Util
    connect Controller to target.Controller, target.Model, target.Util
    connect DataAccess to target.DataAccess, target.Model, target.Util
    connect Model to target.Model, target.Util
    connect Util to target.Util
}
 
template Components : Layered
{
    include "**/com/hello2morrow/(*)/**"
    exclude "**/com/hello2morrow/framework/**"
 
    artifact capitalize($1)+"Component"
    {
        apply "layering"
    }
    connect all using C2C
}
// ...

Now you have total control about the way components access each other. The connection scheme determines for each of the layers which layers can be used in the target artifact. The “connect all” statement declares the connection scheme to be used. The scheme has to connect an artifact class to itself (“Layered” to “Layered” in this example) and prototype artifact must be of the matching class. In our example that happens implicitly by using the class on the template.

Please let me know what you think about this new feature by adding a comment below. Would that be useful in your organization? Do you have ideas how this approach can be improved?

Leave a Reply

Your email address will not be published. Required fields are marked *