Hello,

As Niclas mentioned earlier in another thread, it is probably best to get terminology straight when talking about the Java and C# versions of Zest. For new people or those who just happened to forgot, I am the person who "ported" Zest from Java to C#, so I guess I am the right person to explain how things are on C# side. I assume here that readers are at least somewhat familiar with Zest codebase and how things work 'under the hood'.

I am going to talk about "Qi4CS" as "Zest ported to C#" since that is the current name of the framework (since Zest used to be Qi4j). There has been talk about Qi4CS moving to be as sub-project of Zest, but that will happen after I've refactored the code generation framework Qi4CS uses, and when I have time and energy to face the bureaucracy of that moving operation. So for now, "Qi4CS" is basically Zest on C#, and just "Zest" is Zest on Java.

My knowledge of Zest codebase might be outdated for some parts, so feel free to correct me when I say something that isn't anymore in Zest codebase. This is quite lengthy mail, but I think and hope that reading all of this will create some thoughts and pondering for Zest as well. :)



To start, Qi4CS has the same architectural principle as Zest, namely having a Core and then Extensions to interact with other frameworks. The Qi4CS Core has same components as Zest Core, namely "API", "SPI", "Bootstrap", and "Runtime". They have roughly the same responsibilities as in Zest as well, where API types are meant to average every-day user of Qi4CS/Zest, SPI types are meant for people writing Extensions/Libraries/etc for Qi4CS/Zest, and Bootstrap types are meant for average user to set up a Qi4CS/Zest application. The first difference comes in Runtime component: Zest Runtime is "private implementation" of API and SPI components, where Qi4CS Runtime is "default implementation" of API and SPI components, additionally exposing some types to create more complex Qi4CS Extensions (more of that later).

All the main components of Qi4CS Core (and Extensions) are structured by having 3 layers: Assembling, Model, and Instance. The Assembling layer contains types relevant to bootstrap stage of the Qi4CS application. The Model layer contains types relevant to code generation related to and verification of Qi4CS application. Finally, the Instance layer contains types relevant to running and using the Qi4CS application, and instantiating and using composites.

The Qi4CS provides two composite types: Plain Composite and Service Composite. The Plain Composite is roughly equivalent to Transient Composite in Zest, and Service Composite is exactly the same as in Zest. There is no Entity Composites in Qi4CS (Core), because I think the concept is too application-specific to be included to Core (in the projects I've worked, the current entity concepts in Zest are unfeasable due to very unusual requirements and design of the environment as a whole). Nor are there Value Composites either, since I found them to bring little new to the Plain Composites. To counteract the lack of composite types, the Qi4CS Core provides types and mechanisms to include new composite types (for example, Entity Composite similar to the one in Zest) via Extensions. These kind of Extensions are the "more complex Qi4CS Extensions" mentioned earlier, and the mechanisms to implement this kind of functionality are found in Runtime Core component. On a better side (IMO), the Core is thus much more light-weight, since a lot of stuff is now missing from there: all Entity-related things (Entity Composite, Entity Stores, Indexing Stores) and Query API/SPI.

Starting to assemble Qi4CS application always starts with the selection of suitable architecture. In Zest, it is assumed that all applications adhere to layered architecture, and indeed almost any other architectures can be modeled using layered architecture. The Qi4CS Core works differently, it provides two architectures (Singleton and Layered) in Core, and also provides mechanisms and types to create custom architectures, should the need arise. Because of this, the Qi4CS Core actually has one more architectural component, in addition to API, SPI, Bootstrap and Runtime: Architectures. This component corresponds more to the "private implementation" style present in Zest Core Runtime, and contains the implementation of Singleton and Layered architectures.

During bootstrap stage, the composites in Qi4CS are added to the architecture-neutral unit called Assembler. Singleton architecture has one assembler, accessible directly from the main architecture instance. Layred architecture has layers, each containing modules, and the modules then have the assembler. Notice that unlike Zest, layers and layered architecture itself can not hold composite declarations, as they do not have assemblers. This was initially done for simplicity's sake, and currently there has not been any need to change that. All composite declarations have familiar (for Zest user) functionality related to fragments: Mixins, Concerns, Side-Effects and Constraints. All bootstrap-related types are located in Assembling -layer of Qi4CS Core and Extensions.

Once the architecture has been assembled, a model for the application and composites is created based on the composite declarations in architecture. Each composite model has a coherent and simple way of examining what kind of methods it has, and how various fragments participate in each method. All of the models are verifiable, and creating application instance is only possible for verified and valid models.
Like in Zest, there is code generation involved.
However, since CLR type system and the VM in general is much more complex than Java, the code generation is optimized to happen during compilation stage. It is possible to generate code programmatically, but one will need the "SDK" edition of Qi4CS for that. All model-related types are located in Model-layer of Qi4CS Core and Extensions.

After model is created, validated and the code has been generated, the application instance may be created. Just like in Zest, the Qi4CS application instance has Name, Version and Mode (although I am considering removing these, currently there are not useful at all, and IIRC they are not present in Zest anymore either?). And just like in Zest, the Qi4CS application has to be activated before it can be used. All instance-related types are located in Instance-layer of Qi4CS Core and Extensions.

The Activation and composite Lifecycle hooks in Zest are accessible by implementing Activatable and Initializable (IIRC) interfaces. The Qi4CS takes different approach: one can annotate the methods on fragment types with attributes (C# attributes are roughly same as Java annotations) called Activation, Deactivation, and Prototype. I think this was something that Niclas suggested on Qi4j list ages ago, and since I was writing Qi4CS at that point, I decided that instead of using interface-implementation-based approach, I would use this attribute-based approach. Personally, I think this approach worked out a lot more better than interface-oriented approach, since I can use method parameters to inject stuff needed only at certain lifecycle stage (instead of creating and injecting to fields). The "hook" methods will be called by Qi4CS in appropriate lifecycle stage of the composite. Methods marked with Prototype attribute will be called when the composite is instantiated from the composite builder. Methods marked with Activation/Deactivation attribute are only meaningful for Service Composites, and will be called when the Service Composite is activated/deactivated, respectively.

I've also changed the semantics of @Mixins, @SideEffects and @Concerns annotation a bit. In Qi4CS, they are called DefaultMixins, DefaultSideEffects and DefaultConcerns, because the runtime will look up fragments from these attributes only if no suitable fragments are provided via "contextful methods" (e.g. WithMixins, WithSideEffects, WithConcerns methods found on composite declaration during bootstrap stage). Rationale for this is that IMO the person who actually assembles the application should have maximum control over what kind of implementation there actually is, and should have ability to "override" the default behaviour.

The ModuleInstance in Zest seems to have a lot of responsibilities (being architectural unit, also providing composite factories, service finders, entity and uow stuff, etc). In Qi4CS, these responsibilities are moved to a architecture-independent unit (on instance level) called Structure Service Provider. In singleton architecture, the application instance has one SSP directly accessible from it. In layered architecture, each module instance has one SSP accessible from it.
The SSP may also be accessed via injections.

The functionality similar to Configuration Composites in Zest is present in Qi4CS, but they are not a composite type by themselves. Instead, there is a ConfigurationManager service, which will create ConfigurationInstance<T> plain composites (yes, generic type composite!), where T is actual type of configuration composite. Usually T is other plain composite as well, its state is populated during deserialization stage. The Configuration Composite concept is completely decoupled from Service Composite concept.

I also combined the concepts of MetaInfo and @Uses-injection into one in Qi4CS. I found them to be very similar (both are used to pass data from "outside" of the Qi4CS/Zest scope), and compacted them into one. The Uses-injection accepts optional String to distinguish between different meaning of the object of same type ("named" objects). For example, one could have two fields, both of integer type, one with [Uses("MyFirst")] and second with just [Uses] attributes. When e.g. creating composite (or already at bootstrap/model stage), one can provide the value for first integer calling .Use("MyFirst", 5) and the value for second integer by calling .Use(6) methods.

For model layer, it is sufficient to say that I pretty much wrote that from blank state. Each composite model has Composite Method Models, Special Method Models, Constructor Models, and Field Models. Composite Method Models then each have Parameter Models, Concern Method Models, Side Effect Method Models and one Mixin Method Model. Parameter Models describe possible injections and constraints this Composite Method Model has. Each of the Concern/Side Effect/Mixin Method Model represents a single method from a single fragment, and thus the control flow of this Composite Method Model can be deduced from those models. Special Method Models are any method of the fragments marked with "special" attribute (e.g. Activation/Deactivation/Prototype discussed above), and also have Parameter Models to describe the parameters. Constructor Models are for each constructor of fragments that the composite is made of, also having Parameter Models to describe parameters.
Field Models describe the injected fields of the fragments.

Since C# has concept of properties defined in its language, there is no Property<T> in Qi4CS. Instead, in order to create a property which is part of composite state, one does something like "String MyProperty { get; set; }" for interface, and then just leaves the implementation out (by having fragments implementing that interface leaving the property as abstract). The Qi4CS detects this situation and during creation of the model, delegates the implementation of the setter and getter -methods to the generic mixin.
(The similar logic works for C# events, for those familiar of them.)


Phew, what a long mail.
I've most certainly forgot some stuff, but feel free to ask and discuss the things I mentioned so far! :)

Reply via email to