11.10.  Artifact Classes

Artifact classes have been added as an optional and advanced feature that can be really useful in larger projects or in conjunction with connection schemes. An artifact class basically names the connectors and interfaces an artifact is supposed to have. If an artifact is declared to have a specific class Sonargraph will verify that it defines all the interfaces and connectors required by the class. Moreover connection schemes can now also define source and target classes which allows immediate checking of correctness.

Another benefit is that artifact classes make it a lot easier to organize artifacts into a tree so that the number of top-level artifacts stays manageable.

Let us introduce a real example:

// File "layering.arc"
artifact Service
{
    // ...
    connect to Controller
}  
artifact Controller
{
    // ...
    connect to DataAccess
}
artifact DataAccess
{
    // ...
}
public exposed artifact Model
{
    // ...
}
interface IService
{
    export Service, Model
}

// Main file "business.arc"
class BusinessComponent
{
    interface IService, Model
    connector Controller, Model 
}

connection-scheme BC2BC : BusinessComponent to BusinessComponent
{
    connect Controller to target.IService
    connect Model to target.Model 
}

artifact Customer : BusinessComponent
{
    apply "layering"
}

artifact Order : BusinessComponent
{
    apply "layering"
    
    connect to Customer using BC2BC
}
  

The artifacts "Customer" and "Product" are specifying "BusinessComponent" as their artifact class. Therefore they must have "IService" and "Model" either as an interface or as an exposed artifact. They also must have connectors or artifacts named "Controller" and "Model". In our example the artifacts conform to the class. Otherwise Sonargraph would report an error.

The advantage of using artifact classes together with connection schemes: Now we can check the connection scheme for correctness at the point of definition. Without the use of classes we can only do checks at the point of use.

Another aspect of artifact classes is that they help grouping components together in an elegant way. Let's look at another example:

artifact OrderProcessing : BusinessComponent
{
    local artifact Customer : BusinessComponent
    {
        apply "layering" // see above
    }
    artifact Order : BusinessComponent
    {
        apply "layering"
        connect to Customer using BC2BC // defined above
    }
    connect to ProductManagement using BC2BC 
}

artifact ProductManagement : BusinessComponent
{
    artifact Product : BusinessComponent
    {
        apply "layering"
        connect to Part using BC2BC
    }
    hidden artifact Part : BusinessComponent
    {
        apply "layering"
    }
}
  

The first thing you should notice is that neither "OrderProcessing" nor "ProductManagement" define the interfaces and connectors required by "BusinessComponent". They don't have to, because their nested artifacts do provide those connectors and interfaces. If an artifact belongs to a class and does not explicitly define a required interface or connector Sonargraph will check if it has nested artifacts that do.

In the case of interfaces Sonargraph will implicitly create a missing interface by exporting the matching interfaces of nested artifacts that are not hidden. In the case of connectors Sonargraph will implicitly create a missing connector by including the matching connectors of nested artifacts that are not local.

Here is the same example with all those implicitly defined interfaces and connectors explicitly defined:

artifact OrderProcessing : BusinessComponent
{
    local artifact Customer : BusinessComponent
    {
        apply "layering" // see above
    }
    artifact Order : BusinessComponent
    {
        apply "layering"
        connect to Customer using BC2BC // defined above
    }
    // Implicitly defined
    connector Controller
    {
        include any.Controller  // will not include Customer.Controller because Customer is local
    }
    connector Model
    {
        include any.Model       // will not include Customer.Model because Customer is local
    }
    interface IService
    {
        export any.IService
    }
    interface Model
    {
        export any.Model
    }
    // end of implicit definitions
    connect to ProductManagement using BC2BC 
}

artifact ProductManagement : BusinessComponent
{
    artifact Product
    {
        apply "layering"
        connect to Part using BC2BC
    }
    hidden artifact Part
    {
        apply "layering"
    }
    // Implicitly defined
    connector Controller
    {
        include any.Controller 
    }
    connector Model
    {
        include any.Model
    }
    interface IService
    {
        export any.IService // will not include Part.IService because Part is hidden
    }
    interface Model
    {
        export any.Model    // will not include Part.Model because Part is hidden
    }
    // end of implicit definitions
}
  

The implicit definitions only occur when you do not make an explicit definition. So you can always override those definitions although this should hardly ever be necessary.

Using artifact classes can become a very powerful pattern especially for the design of larger systems with many components that have a similar internal structure.

You can also use classes to define connections:

artifact C1 : BusinessComponent
{
    apply "layering"
    connect to class BusinessComponent using BC2BC
}

artifact C2 : BusinessComponent
{
    apply "layering"
    connect to class BusinessComponent using BC2BC
}

artifact C3 : BusinessComponent
{
    apply "layering"
    connect to class BusinessComponent using BC2BC
}
  

This allows each of the 3 components to talk to the two other using the given connection scheme. Since this approach will lead to cyclic dependencies between artifacts the cycle check for architectures using this feature is disabled.