Segregation and Integration of Object Views in a

0 downloads 0 Views 344KB Size Report
Keywords: Domain Model, object views, C++ policy, logic programming, ... Abstract The proper design of a Domain Model enables business learning and ..... Alexandrescu A., Modern C++ Design: Generic Programming and Design Patterns,.
Segregation and Integration of Object Views in a Domain Model using DSL (Domain Specific Language) and C++ Policy Guillermo G. Pantaleo* *Associated Professor, Department of Computer Science, School of Engineering, University of Buenos Aires. Mail: [email protected]

Keywords: Domain Model, object views, C++ policy, logic programming, Domain Specific Language.

Abstract The proper design of a Domain Model enables business learning and testing in a fast and flexible way. For this reason, in the construction of a Domain Model it is essential to capture different business views, such as structure and relationship of the entities it comprises, initialization/configuration of the entity properties, business rules to be validated while the objects of the model interact, and memorization of the states they go through. In this article we propose the decomposition of these views with different incumbencies using some DSL (Domain Specific Language) techniques, and their subsequent integration using C++ policy. Working with this strategy, an software architecture is proposed for the construction of a Domain Model. 1.

Introduction

Domain Model and Object-Oriented Programming: Regardless of the methodology used, the business-oriented software-development model is a good practice, well recognized and partially settled in the software development community [1]. A tactic aligned with this working method is the construction of a Domain Model whose objects come from concepts extracted from the problem domain [2]. Advantages of software developed from an object-oriented Domain Model: •

It aids the understanding of the business throughout its component entities and the business rules that validate and control its development. [5]



A set of tests checks the Domain Model behavior and establishes the groundwork for a design targeted to satisfying the non-functional requirements of the system under construction.

Common deficiencies in the construction of an object-oriented Domain Model [3]: •

Technology, with its complexities, has distracted developers from this process, then systems where technology issues and domain are mixed up, leading to coupled software of difficult upgrade and maintenance, hard to understand and test, are commonly found.



Another bias of the currently predominant programming model is the widespread use of object-oriented programming (OOP) with usage abuses. The 1

reason for these abuses lies in trying to use objects for issues that exceed their supporting paradigm, such as the logic implemented in the validation of business rules. Our proposal solves these problems through segregation of the different views. Now we will deal with what information the different views of the model provide and how this information should be expressed. 1.1. What

Domain Model Views Show

The OOP is a valuable modeling technique with related entities from aggregations, inheritance or generalizations. But it is used for modeling other aspects, such as the validation of logical rules. In order to decouple the different views of an object in an object model that represents a business, our proposal is based on the decomposition of views as shown in Figure 1. Four views have been decomposed: •

Entities and their related items



Initialization/Configuration of properties or attributes



Validation of Business Rules



State memorization

Figure 1. Decomposition of the Domain Object Model in its different views.

In Figure 1, the original view only keeps the entities and items related to them (business class and related business class); in the other views, there are other segregated aspects. Thus the property-initialization view constitutes the configuration of objects to instantiate (initial configuration), the business-rules view contains the validation logic rules (logic validation), and the persistence view has the state memorization. The objects of a Domain Model can have all or some of the views shown. Therefore, it is necessary to be able to compose these object classes in a flexible manner so as to add the necessary views when required, according to the concepts modeling the classes. We have included the state memorization, not as artifact of a design due to non-functional requirements associated with persistence, but as a way of characterizing a domain that requires the analysis of the evolution and history of their objects. 2

Our goal is fast and flexible business learning and testing, and to achieve it we need to define an software architecture to build the Domain Model, which is the topic in the next section. 2.

How to Build a Useful Domain Model – Guidelines for Architecture and Design

We have employed the following guidelines for design: •

Minimum-effort integration of views



Initialization of objects in a valid state based on configuration values



Validation of business rules according to collaboration patterns presented by Nicola [5].



Memorization and retrieval of object states on the basis of a business interface not contaminated by artifacts of persistence (SQL code)



Easily implemented trial tests for each view.

As we will see later on, the final goal is the construction of a framework which allows experimenting, learning and testing a business by reusing a set of previously defined objects. Figure 2 shows a diagram of the components of the proposed architecture. The design of each component is discussed in the following sections. The arrows in the figure indicate dependencies.

Figure 2. Relationship between the components of the proposed architecture.

This architecture requires a design that facilitates independent and simultaneous working of the different views that can be integrated or segregated when necessary. The first step is integration. 2.1. Integrating

the Views - The Power of C++ Policy

Before analyzing the view segregation design, we must define how we will integrate the views in order to establish a high-level view that provides a context for the solution before 3

the design is introduced in detail. A strategy developed based on C++ templates and allowing the sum of functionalities from different classes in a host class is called “policy” [4]. Advantages of C++ policy: •

It offers great flexibility to changes promoted by the reuse of different classes implemented by different policies.



The strategy of aggregating different functionalities provided by the policy classes results in light and changeable classes as opposed to the relationship between classes established by the inheritance mechanism generating rigid hierarchies of heavy classes.

In our case, the sum components are the different views of the proposed model. The code below shows the host class that integrates the three views. The type parameters correspond to S: initialization view, T: rule-validation view, U: state-memorization view, and W: type of instance in the domain to create. When the domain classes do not need the integration of some views, they just need not be included. There could be more than one type parameter –it is common to have to indicate the object type of the class to instantiate. template< class S, class T, class U, class W > class Host_CFG_BR_MEM : public S, public T, public U { public: Host_CFG_BR_MEM(W& type): S(type), T(type), U(type){} virtual ~Host_CFG_BR_MEM(){} };

This design has the following advantage; the integration of each view separately makes the intent to avoid a single deep hierarchy with specialized classes inherited from a common root clear. Instead, we have parallel branches implementing each view from one root. This root is the domain class representing the concept of the model. Figure 3 shows this structure in a UML class diagram for the class Container of the collaboration pattern called Container-Content which we will use as an example to present in this article [5]. This pattern is simply an aggregation of content in a container. Classes modeling the domain concepts must state in their functions all the functionality that will then be distributed among the different views. Below are some guidelines for programming these classes. 4

Figure 3. Classes implementing integrated views by use of a C + + "policy". Each view consists of host classes called ConfigurationSetUp, ValidationSetUp and MemorizationSetUp. They are the sum of the business class and corresponding view. At the same time, these classes make up the host class Host_CFG_BR_MEM that represents the sum of all views. The classes in between, i.e., ContainerCFG, ContainerBR and ContainerMEM, implement the functionalities of each view. Since the design of every branch of the hierarchy responds to the same criteria, we describe only one of them below. 2.2. Programming

Business Classes

The classes of the proposed design represent a container and its contents. This is an aggregation and it was proposed as one of the collaboration patterns by Nicola [5]. In the following list you can see the statement of the Container class.

5

In the list, a set of pure virtual functions with the purpose of validating business rules that are assigned to the pattern classes with the strategy proposed by Nicola [5] is show. These are multiplicity and property rules. In the Content class there are other categories: state, type and conflict [5]. The implementation of the above functions is assigned to the rule-validation view. The allocation of responsibility to validate the rules was done following the strategy that the classes having the information should validate the rules, in collaboration with those making up each pattern. To avoid duplicate codes, invocation of validations was turned to the Content class in this case [5]. A method of this class and invocations to the rules can be observed in the list that follows.

6

7

Independence of the views is achieved through the described design and using some techniques catalogued as DSL [6], such as Delimiter-Directed Translation for the initial configuration and Production Rule System to validate the rules. This requires the segregation of the views, and we will see how to implement this in the following section. 2.3. Segregating

the Views, Separating Incumbencies

The key to view segregation is the existence of different inheritance hierarchies for each view, which also promotes the separation of incumbencies. Figure 3 shows that each class implemented by the view inherited from the business class must redefine the functions responsibility has been assigned to, as shown in the previous section. In the case of rulevalidation, the design of this view is represented in more detail in Figure 4. The list below shows one of the functions performed by the validations and the function that sets the syntax of the Prolog query. The Prolog machine is the Production Rule System in our case, implemented over the rule-validation view and segregated. void ContainerBR::TestAddContent(){ ValidatesBusinessRule(this, &ContainerBR::BuildRuleTestAddContent); } void ContainerBR::BuildRuleTestAddContent(){ rules->Clear(); string stringQuery = "["; stringQuery += "container_"; stringQuery += containerType.Name(); stringQuery += "]"; BuildConsult("isFreeSpace", "Container Full", GetFreeSpace(), stringQuery); }

The template function of the ConnectorValidation class that instantiates the Command and turns it to a parameter in the “host” class is shown in the list below. The BusinessRule class wraps the Prolog query in strings and the error message to be displayed in case the validation results invalid. It also wraps the logic result of the validation in “boolean” format. template void ValidatesBusinessRule(T* responsible, void (T::*buildBusinessRuleTest)()){ if(businessRuleBuilder != 0){ delete businessRuleBuilder;

8

businessRuleBuilder = 0; } businessRuleBuilder = new SimpleCommand(responsible, buildBusinessRuleTest); HookTestRule(businessRuleBuilder); }

By using this design, you can avoid the use of any downcast and you can keep the class needing a service decoupled from the one providing it. The class needing a service is in this case ContainerBR, and the one providing it is the “host” class ValidationSetUp that supplies the Prolog machine. This design allows for the separation of incumbencies. We will see now how each view implements their respective functionality. 2.4. How

to Validate Business Rules

We will start with the validation of business rules. As discussed in the previous section and shown in Figure 4, the validator integration for rule validation was implemented as shown in the list below. Clearly, the substitution of one SWI-Prolog validator for another requires little work at the level of this sum using “policy” and “host” classes. template< class T, class W, class U = ValidatorSWI, template class BusinessValidator = Validator > class ValidationSetUp : public T { public: ValidationSetUp(W& tipo): T(tipo), validator(0){ T::Init(tipo); validator = new Validator(); validator->SetComponent(new U()); } // ------------------- command reception -------------------

9

void HookTestRule(Command* buildRuleSyntax){ buildRuleSyntax->Execute(); ValidatesBusinessRule(); } ... private: // ------------------- validation rules ------------------void ValidatesBusinessRule(){ bool isValid = validator->Test(*T::GetBusinessRules()); if(!isValid){ throw InvalidBusinessRule(T::GetBusinessRules()->GetInvalidationMessage()); } } };

Figure 4. Design of the hierarchy branch implemented by the rule validation view. The ContainerBR class will provide the functionality. The “host” class that makes up this view will provide the validator –in this case, an inference machine (SWI-Prolog). The ContainerBR class was created so that the validation rules can be delegated to the validator without information about what rule to validate and also for the propagation channel for these invocations to be unique for all rules. All the validations use this class service that makes the validator get to the business rule

10

through a Command pattern [7]. This unique channel was implemented with a pure virtual function definition HookTestBusinessRule(Command) that is redefined in the “host” class ValidationSetUp.

The dynamics of the invocation propagation when a rule is validated can be observed in Figure 5. This same design was used in the ConfigurationSetUp class to provide the parser with a configuration associated with the initialization of object attributes and the MemorizattionSetUp class to provide access to the database for the persistence of object states. For the first one, a Delimiter-Directed Translation [6] parser was developed, and for the second one, SOCI [8] based on a MySql [9] database was used.

Figure 5. Invocation sequence for rule validation. Sequence diagram that summarizes the validation sequence of events in a rule validation starting with the invocation of one of the TestXXX functions stated in the business class –Container in this case.

The list below shows some facts and queries of the Prolog code used in the example described. This code is invoked from the ValidatorSWI class of C++ objects based on business rules assembled as text strings and converted to API SWI-Prolog/C++ [10] objects. The connection with the SWI-Prolog machine represents a scheme similar to the DSL “Foreign Code” [6] pattern where the queries are built as strings which are then mapped to “terms” [10]. %---------------- facts ----------------------containerType(cooled). contentType(food). areCompatibleContent(food, food):- !.

11

areCompatibleContent(food, _) :- fail. isTypeCompatible(cooled, food). isTypeCompatible(cooled, _ ):- fail. containerState(unRefrigerated). ... contentState(consumerFriendly). ... %---------------- consults -------------------isFreeSpace(FreeSpace, ContainerType):- atom_number(FreeSpace, X), consultoConfiguracion(ContainerType, 'MAX_FREE_SPACE', Value), X =< Value, X > 0, !.

When describing the implementation of views we will continue with the view that models the initial configuration. This also shows how objects are constructed and what associated problems we must solve. 2.5. How

to Set Properties

Configuration uses an INI file with sections for each class and type. Therefore, the parser consults this INI file and gets from it the information needed for initialization of the objects. In the list below, a fragment of this configuration file is shown. [container_refrigerated] volume = 20 temperature = 0 temperatureTolerance = 2 MAX_FREE_SPACE = 2 ... [content_food] volume = 0.100 maintenanceTemperature = 0 expirationDate = 2012 ...

For these files parser we use the pattern commonly used in DSL called Delimiter Directed Translation [6] programmed in C + +. 12

As a design guideline, it was proposed that the objects be constructed in a valid state. To achieve this, before they become available for the programmer, they should be initialized based on the configuration mechanism proposed. As previously mentioned, the design is similar to that of the validation view. However, it has some complexities typical of object construction. In C++, the objects are built starting from the base parts to the derivative parts. According to our design, the initialization is performed in the HookInitializesProperties(Command*) function in the ConfigurationSetUp “host” class after the sequence of events in the InitializesProperties function has started based on their re-definition of the intermediate class, ContainerCFG. Generating the event that invokes such function is a problem, because if it is invoked with the objects already built, since the attributes have not been initialized at defined values, these objects are in an inconsistent state. On the other hand, the function cannot be invoked in higher classes of the hierarchy because the configurator used by the configuration parser is only available after the derivative part of the objects supported by the ConfigurationSetUp is built. We resorted to a language using templates and allowing the initialization invocation before the objects are accessible. The list below shows the usage of a template structure to achieve this goal. template struct InitializerWParameter : T{ InitializerWParameter(U& type): T(type){ T::InitializesProperties(); } ... static InitializerWParameter* Instantiated(U& type) { return new InitializerWParameter(type);} };

This structure is used with the “host” class parameter resulting from the Domain Model. It is also important to stress that due to the use of multiple inheritance, in the cases of classes having constructors with parameters, the construction should be supplemented with some initialization method invoked forcefully because of the C++ construction of the base part of the hierarchy by default constructors [11]. The way to declare and instantiate domain objects using the proposed design is shown in the list below. //----------------------------------------------------------------------------------------------//

Domain Model Initialization

//----------------------------------------------------------------------------------------------typedef Host_CFG_BR_MEM Host_Container;

typedef InitializerWParameter ContainerDomain;

typedef Host_CFG_BR Host_Content;

typedef InitializerWParameter ContentDomain;

ContainerDomain* containerCooled = ContainerDomain::Instantiated(ContainerType::REFRIGERATED()); ContentDomain* meat = ContentDomain::Instantiated(ContentType::FOOD()); ...

Although the view for memorizing object states is the simplest, it presented some problems that were solved as follows. 2.6. How

to Memorize States

The view design is similar to that of the other views regarding event propagation in a decoupled manner. The classes administrating memorization are ContainerMEM and ContentMEM. The MemorizationSetUp class provides the database with the connector, which can be changed with minimum effort at this “host” class level. The persistence of the Content aggregated classes is administrated by the persistence view of the root class in the aggregation –Container in this case. The Factory pattern [4] was used for its implementation to decouple the object creation in the ContentMEM class within the code context of the appropriate Container class. Atomic and independent tests are possible through the presented design, which allows each view to be exercised separately and one at a time. We will see some of them. 3.

Some Tests

Some of the tests performed on the container in the example and its contents are shown in the list below. The test of each independent view can be observed. 14

CPUNIT_TEST_EX(BusinessRulesTest, test_overLoad, InvalidBusinessRule) { ...} CPUNIT_TEST(BusinessRulesTest, test_configuration) { ...} CPUNIT_TEST(BusinessRulesTest, test_container_memorized) { ...} For reasons of space we do not include the full testing code, yet emphasizes that our strategy enables test each view separately. This property is desirable because each one of the views has different characteristics and problems. These tests were implemented using Eclipse CDT [12] and CPUnit [13].

Conclusion Domain Model construction is a fast way to guarantee the understanding of business, facilitating thus other project tasks. This model should allow the exploration and validation of the business based on a set of tests that analyze it. With this goal, in the construction of a Domain Model it is essential to capture different business views, such as structure and relationship of the entities it comprises, initialization/configuration of the entity properties, business rules to be validated while the objects of the model interact, and memorization of the states they go through. In this article we introduced a Domain Model based on the segregation of the views the object is made of, using collaboration patterns, C++ “policy” and DSL patterns. The proposal was developed using a simple example, however we are extending this idea to complex domain models resolution using generation code technics [14] that will be published in a future paper.

15

About the Author

Guillermo Pantaleo is an Associated Professor in the Department of Computer Science, School of Engineering, University of Buenos Aires. He specializes in teaching object-oriented design and programming. As a software engineer consultant in it-Mentor, he mentors in software process improvement, software development projects for instrumentation, digital signal processing and information management. Pantaleo is the author of the books Calidad en el Desarrollo de Software (Quality in Software Development) and Ingeniería de Software (Software Engineering).

References [1]

Palmer S., Felsing J., Feature Driven Development. Prentice Hall PTR – The Coad Series, 2002.

[2]

Booch G., Object Oriented Programming and Design with applications, 2nd edition, The Benjamin Cummings, 1994.

[3]

Pantaleo G., Calidad en el Desarrollo de Software, Alfaomega, 2011.

[4]

Alexandrescu A., Modern C++ Design: Generic Programming and Design Patterns, Addison Wesley, 2001.

[5]

Nicola J. et al, Streamlined Object Modeling. Prentice Hall PTR – The Coad Series, 2002.

[6]

Fowler M., Domain-Specific Languages, Addison Wesley, 2010.

[7]

Gamma H. et al, Design Patterns, Addison Wesley, 1994.

[8]

SOCI - The C++ Database Access Library, http://soci.sourceforge.net/

[9]

MySql database, http://www.mysql.com/

[10]

Prolog Programming Language, http://www.swi-prolog.org/

[11]

Meyers S., Effective C++: 55 Specific Ways to Improve Your Programs and Designs. Addison Wesley, 2nd edition, 1998.

[12]

Eclipse CDT - C/C++ Development Tooling, http://www.eclipse.org/cdt/

[13]

Test framework, http://cpunit.sourceforge.net/

[14]

Herrington J., Code Generation in Action, Manning Publications, 2003.

16