ColdFrame: Domains

Definition: a domain is a separate real, hypothetical, or abstract world inhabited by a distinct set of classes that behave according to rules and policies characteristic of that domain.

Motivation

Separation of concerns

The main reason for using domains is that it simplifies the analyst's job. It's hard enough to think about one subject matter at a time, without mixing them up.

A prime example is input/output; if you're considering the rules of engagement for a weapon system, you really don't want to be worrying about how the data gets into and out of the system.

Separation of activity

Most CASE tools are pretty bad at allowing more than one person to access the model at once. If you can arrange that a group of classes and relationships can be treated as a group, by minimising the interactions with other groups of classes as far as possible, you stand a better chance of getting the tool to do what you need.

Software management

Another reason is to provide sensible work packages!

Modelling

Given the above, the obvious way to model domains is as packages in the logical model, stereotyped «domain».

Domain packages can have child packages. This might be to make the model structure clearer (for example, by separating off all the type definitions). Alternatively, they might contain model statements that aren't intended to be translated; such child packages can be stereotyped «ignore» (to be honest, any package in the model which is stereotyped «ignore» will be ignored, as will all its children, recursively).

Tagged values

init = package.operation specifies an operation to be called before any other domain initialization.

name = package name specifies the actual package name.

revision = revision-id-string specifies the domain's version control ID.

Testing

For testing purposes, it may be convenient to gather the «public» classes and public types of a domain into a child package stereotyped «domain-interface». The package name should be Domain Interface, which controls where the generated code is placed, and the tagged value name=Domain set so that the Ada package names match those of the actual software.

Translation

A domain is translated as a top-level Ada package, with the name derived from the package name using the usual name conversion rules (it can be overridden using the {name=new-name} tag).

Child UML packages are included (recursively). The child UML package structure is not reflected in the generated code (for example, all classes end up as direct Ada child packages of the domain package).

Domain types are implemented in the domain package. For string types, a string hash function is generated as a child subprogram of the domain package.

Domain classes are implemented as children of the domain package.

Six further children are generated as standard:

Domain.Events
Provides Event queueing and dispatching.
Domain.Initialize
Takes an Event Queue parameter, defaulting to null.
Initializes the domain by
  1. initializing the domain's Event Queue with the supplied Event Queue, or (if it's null) by calling Events.Initialize,
  2. calling the domain initialization operation, if one was specified by { init = package.operation },
  3. initializing all class attributes and singletons via Class.CF_Class_Initialize,
  4. and then calling all the «init» operations of all the classes.

The calls are in alphabetical order by class and then by operation.

See also further discussion.

Domain.Tear_Down
For testing purposes; returns the domain to its initial state by
  1. calling Domain.Events.Tear_Down, which stops the domain's Event Queue and deletes it if this was the last reference,
  2. deleting all the class instances and associations.
Domain.Cascade_Initialize
If you need the services of a domain, you have (of course) to include all the domains that it uses in the build process, At run time, these domains need to be initialized. The idea behind Cascade_Initialize is to make this process easier, especially for domain-level testing, and more especially where you are using "real" versions of the domains you depend on rather than stubbed ones.
It takes an Event Queue parameter, defaulting to null. The generated code merely calls Initialize with the supplied Event Queue.
To use it as intended, modify it to
  1. call Cascade_Initialize for all called domains, with the supplied Event Queue,
  2. call its own Initialize, with the supplied Event Queue,
  3. and then call Cascade_Initialize for any domains known to require this one (eg, device bottom halves), again with the supplied Event Queue.
If you are testing against stubbed domains, or you're building a real program, you'll be better off not using Cascade_Initialize; instead, initialize all the used domains in an appropriate order.
Domain.Cascade_Tear_Down
The inverse of Cascade_Initialize.

Use

Each domain must be initialized by calling the procedure Domain.Initialize, probably from your main program. Because it's a child unit, it must be withed explicitly.

If you're building your application, rather than unit tests, you'll probably want to pass Domain.Initialize the Event Queue that Domain is to use.

Because all the packages in a domain are implemented as children, you hardly ever need to mention the domain package when referencing other packages (you do need to mention the domain in the context clauses, though):

with Domain.Other_Class;
separate (Domain.Class)
procedure Foo
is
begin
   Other_Class.Bar;
   ...