Skip to end of metadata
Go to start of metadata

Overview

Proper modularization and decoupling of software is a critical element of software design. It allows for large and complex systems (like Kuali Rice) to be decomposed into smaller pieces. This allows for the software to be more easily understood and reasoned about, additionally improving the ease with which the software can be maintained and evolved.

The Kuali Rice framework currently has a course-grained modular breakdown, but modularity rules are neither documented well nor easily enforced. As a result, the code base for Kuali Rice 1.0.x contains very tight coupling and dependencies in many places. To make matters worse, these undesirable relationships in the code are difficult for the development team to detect and prevent.

These various issues highlight the need for proper modularity principles to be applied to the Kuali Rice codebase. As part of the Version Compatibility work that is being done for Kuali Rice 2.0, this will be a requirement in order to isolate the pieces of the Kuali Rice codebase that need to fall under governance from the perspective of compatibility. However, the work being proposed here for Kuali Rice 2.0 goes beyond the requirements being driven by version compatibility. The goal is to provide a modular framework that will sustain Kuali Rice for a long period of time into the future.

History of Modularity in Kuali Rice

In pre-1.0 version of Kuali Rice, the software was made up of one module per high-level project, as follows:

  • core
  • kns
  • kew
  • ksb
  • kim
  • ken
  • kcb
  • server

One of the major deliverables for Rice 1.0 was converting the KEW module to use the KNS. However, the KNS already had a dependency on the KEW module in Maven. Maven does not allow for circular dependencies between modules. Two solutions were proposed for this:

  1. Split the KNS and KEW modules into multiple sub-modules (api, impl, and web) and then have the appropriate pieces depend on each other.
  2. Combine the modules together in order to hide the circular dependency problems and try to make development simpler.

After discussions in the Kuali Technical Integration working group (KTI), the second option was chosen and the project ended up with the following Maven module breakdown for Rice 1.0:

  • api
  • impl
  • web

Internal to each of these, the package name would be used to indicate which of the original modules the code belonged to, as follows:

  • org.kuali.rice.core.*
  • org.kuali.rice.kns.*
  • org.kuali.rice.kew.*
  • ...etc...

This certainly helped to ease development because developers no longer had to worry about the enforcement of undesirable couplings between software components that Maven was performing. Unfortunately, this had the side-effect during the frantic development on Rice 1.0 that we ended up with coupling in the software in undesirable places.

Examples of Modularity Issues in Rice 1.0.x

In order to shed some light on specific modularity and coupling issues that exist in Kuali Rice currently, here is a list of some examples:

  • In order to configure Rice, you have to create a RiceConfigurer which exists in the impl module. This class has dependencies into all of the different modules of Rice, effectively result in the Rice core depending on everything.
  • If you want to use just an embedded Kuali Enterprise Workflow engine in your client application, but not use the KNS framework to develop any screens, you still need to configure a lot of the KNS pieces. This is directly a result of the concept of a "ModuleConfiguration" being part of the KNS.
  • There are currently many "cross-module" dependencies within the single "impl" module. Some examples:
    • The KEW WorkflowAttribute class depends on the KNS Field class
    • KimAttributes in the KIM impl package depends on the KEW DocumentType class which is also an impl class.
    • The SearchableAttribute class in KEW depends on the KNS Row object, however the DictionaryValidationServiceImpl class in KNS depends on SearchableAttribute, resulting in a circular dependency.
    • KSBModuleConfigurationSpringBeans.xml depends on dataDictionaryService which is in KNSSpringBeans.xml. KNSServiceBusSpringBeans.xml depends on enServiceInvoker which is defined in KSBSpringBeans.xml.
  • There are numerous examples along the lines above. These couplings and dependencies have been allowed to happen and have been undetected as a result of the single impl module that we have right now.
  • Currently in order to get around a lot of these issues the entire KNS is included in the "impl" module. It is very difficult to pull pieces of the KNS up to "api" because of some of these couplings that we are talking about.
  • The Spring dependencies create rigid order dependencies and couplings that are difficult to detect/prevent due to a single SpringContext

Definition of Modularity

The term "modularity" means different things to different people. In general modularity refers to the ability of a system to be defined as a composition of other smaller systems which can be separated and recombined. There are generally dependencies between different modules of a system, but each module has a clear goal and purpose and is responsible for the concerns assigned to it.

In terms of requirements for modularity, we will use the definitions provided by Bertrand Meyer in his book "Object-Oriented Software Construction, 2nd edition" [1]. Meyer presents five fundamental requirements that a design worthy of being called "modular" must satisfy:

  1. Modular Decomposability - A software construction method satisfies Modular Decomposability if it helps in the task of decomposing a software problem into a small number of less complex subproblems, connected by a simple structure, and independent enough to allow further work to proceed separately on each item.
  2. Modular Composability - A method satisfies Modular Composability if it favors the products of software elements which may then be freely combined with each other to produce new systems, possibly in an environment quite different from the one in which they were initially developed.
  3. Modular Understandability - A method favors Modular Understandability if it helps produce software which a human reader can understand each module without having to know the others, or, at worst, by having to examine only a few of the others.
  4. Modular Continuity - A method satisfies Modular Continuity if, in the software architectures that it yields, a small change in the problem specification will trigger a change of just one module, or a small number of modules.
  5. Modular Protection - A method satisfied Modular Protection if it yields architectures in which the effect of an abnormal condition occurring at run time in a module will remain confined to that module, or at worst will only propagate to a few neighboring modules.

Guiding Principles

In general, as we proceed through the re-modularization work in Rice 2.0, we need to ensure that we adhere to some guiding principles to keep the work focused. A list of the most important of these is as follows:

  1. Promote a logical separation of the different pieces of the Rice framework and middleware.
  2. Enforce that separation within the codebase to prevent unwanted coupling and dependencies from making their way into the software.
  3. Eliminate circular dependencies.
  4. Provide a framework that allows for developers to more easily understand where a piece of the Rice software stack will reside, and how it will be used.
  5. Make Kuali Rice easier to configure.
  6. Isolate code or services which a client application should be allowed to interact with from those that should be internal.
  7. Organize code into packages to properly reflect what should be used by developers using the framework and services and what should not, and ensure that the package names effectively communicate their purpose.
  8. Reduce duplication of code by extracting and merging common functionality to an appropriate module.
  9. Move shared utility code (those with few dependencies) to the Rice core.
  10. Get rid of unnecessary or extraneous library usage.
  11. Minimize the number of library dependencies that are required to be included in order to utilize a desired subset of functionality within Rice (i.e. calling services remotely, just using a single module, etc.)
  12. Work with an eye toward a set of defined Use Cases for Kuali Rice integration and deployment.

Use Cases for Modularization

As mentioned in the guiding principles, we want to ensure that we are working with an eye toward a set of defined used cases for how Kuali Rice can be deployed and integrated with.

The following page documents many of these use cases:

Relationship to Version Compatibility

Proper modularization of Kuali Rice is essential to the successful execution of our plans for version compatibility as outlined in the Kuali Rice Version Compatibility Statement.

Modularity allows us to achieve our version compatibility goals by cleanly separating out the portions of the Rice project that encompass the service apis and frameworks that an external client application is permitted to use. By establishing rules surrounding what a client is (and is not) permitted to use, we can know which portions of the codebase need to be placed under compatibility governance. Additionally, it allows us to know that we can make impacting changes to the portion of the codebase which are not permitted for client use without affecting compatibility.

Definition of a Module

The Kuali Rice project consists of a set of modules, each of these modules is made up of a series of sub-modules and falls into one of two categories:

  1. Service Module
  2. Framework Module

Depending on the type of modules, sub-modules will be constructed in a certain way, as described in the next two sections.

Service Module

Service modules represent the bulk of the modules within the Kuali Rice project. Examples of service modules in Kuali Rice include:

  • Kuali Identity Management
  • Kuali Enterprise Workflow
  • Kuali Enterprise Notification
  • Kuali Service Bus

A service component consists of a set of service apis which are used by external modules or components which integrate with them. They will generally have some level of support to be configured such that their services can be invoked remotely, and possibly over multiple transport protocols. Note that most service modules also have framework components to them. A framework component is defined as a class or set of classes that are used to develop functionality that plugs into that particular module. These could include base class implementations from which these various custom components should extend. Examples in KEW would be PostProcessor, WorkflowAttribute, SearchableAttribute, and others. Examples in KIM would include the various "type" services, like KimGroupTypeService, KimRoleTypeService and then their base classes such as KimGroupTypeServiceBase, etc.

Along these lines, a service module will contain a set of standard sub-modules which include:

  • api
  • framework
  • impl
  • web

The following diagram shows the relationship between these different submodules of a service module:

Gliffy Zoom Zoom kim-module-diagram

The description and purpose for each of these sub-modules will be explained in the following sections.

api

The api module will include classes which external clients of the module are allowed to invoke from code that either calls into the various services the module might expose, as well as code which is used to configure and launch the module.

This module will also contain versioned services that are used for remote service invocation. The classes in the module are key to the implementation of version compatibility. In some cases service facades will be provided that sit on top of the versioned services to insulate client applications from underlying versioning-related changes in the services.

Each of the api modules is intended to have very few dependencies and they should depend on, at most, the Rice Core API and the Service Bus API.

The api module will contain the following:

  1. Java interfaces for the various public services the module exposes
  2. Java interfaces for the various business objects which make up the domain model for the module. These will not extend from any KNS/KRAD classes.
  3. Versioned jaxws-annotated service interfaces for the module
  4. Versioned jaxb-annotated DTOs classes for the module
  5. WSDLs for the remote services.
  6. Classes that are used to configure and launch the module, typically wired up in spring files.
  7. The ServiceLocator for the module (for public services)
  8. Constants classes

As a result, the api module will have very few dependencies. This also means that the api module will contain very little logic and, in general, cannot be used by itself without also including the corresponding impl module and configuring the needed portions of it appropriately.

framework

The framework module is intended to contain classes that are used to develop functionality or components that integrate with the module. In many cases, this is where the module might make calls back into client-developed code. A good way to distinguish this is in the following manner:

  • api - contains module code that a client will invoke
  • framework - contains interfaces and classes that a client will extend from that will allow it's code to be invoked by the module

Another reason why the api and framework modules are separate is that the framework module will typically define more then just service contracts. It may contain some base classes that client applications can extend from which provide some level of default behavior. This introduces more outgoing dependencies from the framework module, and will most likely introduce dependencies out to the KRAD module of Rice or to other framework components in other modules.

One important thing to note is that classes and services referenced within a framework class should never come from the impl sub-module of the current module or any other impl module within Rice.

impl

The impl module contains classes that implement the various services defined in the api or framework modules of Rice. It contains all classes behind that service layer that support the implementation of those services. This includes:

  1. Business object implementations that are mapped to the database via OJB/JPA.
  2. Data Access Object interfaces and implementations.
  3. Internal module "services" and their implementations.
  4. Data dictionary files which include business object entries.
  5. Spring files which can be loaded to create the appropriate implementations for the various services (loading of these spring files is controlled by the configurer for the module).

Note that the impl module will likely contain many dependencies, including dependencies back into KRAD for various development framework dependencies. Client applications will need to depend upon the implementation module if they want to use any of the services in an "embedded" fashion. If they are only using the module remotely, then they will need to depend on the remote module instead.

However, it is important to note that impl sub-modules from the various high-level modules should never be interrelated. Each impl module should be completely isolated from it's peers. The impl modules should only interact with api or framework code for cross-module communication.

web

The web module contains classes that implement the web application portions of the module. Since we use KNS/KRAD as the web application development framework for the majority of our modules, there will likely be many dependencies back into this portion of the development framework. Generally speaking, the web module will include:

  1. Struts action implementations
  2. Struts form implementations
  3. Struts-config.xml files
  4. Data Dictionary files used to define the UI (lookups, inquiries, documents, etc.)
  5. JSPs and Taglibs for the web portion of the module
  6. CSS, javascript, and image files for the web portion of the module

It's important to note that the web module plays an important role in web content modularity. See the web content modularity section of this document later for more information on how this will be handled.

Because of the nature of the web module, it will contain more dependencies then the impl module does. It's important that a client application should never need to include the web sub-module of a particular Rice module in their application unless they are running that module in "bundled" mode which means they are loading all of the web pieces into their application.

Framework Module

Examples of framework modules in Kuali Rice include:

  • Core
  • Kuali Rapid Application Development
  • Development Tools

Framework modules are different from service modules because they can be broken down into sub-modules which produce distinct sub-sets of functionality and provide for an optimal isolation and separation of dependencies. It is intended that a framework module will have one or more consumable pieces, not all of which will need to be consumed by a given application. This drives the differences in how they are structured from a modularity perspective in order to achieve an appropriate isolation of related functionality and so that only those modules that a particular client application needs to consume can be used.

To this end, instead of having a fixed set of sub-modules (like api, framework, etc.) a framework-based module is broken down into it's various components and is packaged that way. This is similar in fashion to how frameworks like the Spring Framework are modularized. Note however that this doesn't preclude a framework module from having an api or impl sub-module if there is a need for that.

Modularity on the Java Platform

A Cavalcade of Projects

The Java Platform itself does not currently have built-in support for the concept of modularity. However, the community around Java has been working on specifications for adding support to the platform for modularity and dynamic components over the past few years. Additionally, a modular framework for Java called OSGi was developed which provides a framework for this, however it was originally developed outside of the java community process. There is a long history of discussion on the work done within the java community process (JCP) on the various JSRs, as well as differences of opinion on whether or not OSGi should simply be adopted as the standard. The set of projects and their current status is summarized below (note some of these dates may be off slightly, please read this with a grain of salt):

Project

Description

Status

JSR-277

Java Module System

Inactive as of November 2006

JSR-291

Dynamic Component Support for Java SE

  • Final specification released August 2007
  • Implemented in OSGi 4.1 in May of 2007

JSR-294

Improved Modularity Support in the Java Programming Language

  • Inactive in December 2007
  • Resurrected in December of 2008 when project Jigsaw was started

OSGi

Module system and service platform for Java

  • Has been an active project since 2000.
  • Released specification 4.2 in 2009
  • There are numerous OSGi container implementations available

Project Jigsaw

A modular system for Java which is being implemented to support modularizing the JDK itself

  • Intended to be included as part of Java 8 (late 2012)
  • Is similar to but not the same as OSGi and JSR-294

So one can see from the list above that there has been a lot of thrashing, even with the Java Community Process, in the area of Java modularity. A more succinct history of events can be found here:

For modularization of the JDK itself, the JCP process for Java 7 and Java 8 has decided to build an entirely new module system just for this purpose (Jigsaw). There is much disagreement within the larger Java community on this approach, with many citing the fact that OSGi has been around for a decade and is well established as a solution for modularity in Java. However, work still proceeds on Project Jigsaw. As indicated previously though, this will not be released for a couple more years.

Why not use OSGi to implement modularity in Rice 2.0?

While OSGi may prove to be a good platform for implementation of modules in Rice 2.0, we are not currently planning to rearchitect the system to implement OSGi for this version. There are a few different reasons for this:

  1. One of the primary motivators for the modularity project in Rice 2.0 was to aid in implementing version compatibility for the middleware layer. It's been accepted by the Application Roadmap Committee that framework-layer compatibility can come later. This document previously mentioned the relationship between modularity and version compatibility, and in order to satisfy those requirements we do not need to put OSGi into place. The most critical element of this work is the proper separation and dependency management of the Kuali Rice codebase. All of this can be done without putting a framework like OSGi into place.
  2. Developing with OSGi requires a significant shift in development practices and tooling. Modularity work would be forced to go strictly down to the package level and that is an explicit requirement with OSGi. While we are largely moving toward aligning our package structures with our modularity output, adding this as a strict requirement for Rice 2.0 would add scope that isn't a direct requirement for middleware version compatibility.
  3. Given the above, putting OSGi into place for Rice 2.0 would be considered scope creep, as it is not required to accomplish our version compatibility goals. The Rice 2.0 timeline is very critical and many projects are dependent upon it. Implementation of OSGi in Kuali Rice would be a significant architectural effort and would add a significant number of hours onto the 2.0 schedule which were not originally accounted for.
  4. By cleanly separating our code into modules for Rice 2.0, we better position ourselves for the use of a framework like OSGi. So the effort which we are doing for Rice 2.0 is a necessary first step before we could take proper advantage of an OSGi container.
  5. We feel that we would really need to spend a much longer amount of time looking into OSGi to validate that it will suit our needs and that it is the proper solution for Kuali Rice. OSGi has largely been used on the server side for enterprise applications. It requires an OSGi container to support it. Many of the SOA portions of Rice would fit well into this model, but the Rice project also provides a development framework which can be used by other applications. We should think long and hard about whether we want to impose the requirement on all client applications to have an OSGi container running resident within those applications (even if the management of such a container is effectively "hidden"). There is a valid concern here that this might add an extra amount of weight to our current development framework pieces that are part of Kuali Rice.

Note that OSGi in large part provides a mechanism for packaging modules (known as "bundles" in OSGi), defining what they export for external usage, and defining dependencies between modules. We currently get a lot of this from our usage of Maven which has it's own concept of modules (it's been said that Maven 4 will be "OSGi-based", but it is difficult to find information on what exactly that means). OSGi also provides a way to handle the lifeycycles for those different modules (including the concept of "hot-deployment"). We have a concept of lifecycle management in Rice currently but it is admittedly a bit poor. In order to accomplish our goals for Rice 2.0 this will need to be refactored some, so we should be addressing these kinds of issues as part of that work.

To summarize, research on OSGi support is actually in our roadmap (see KRRM-52), but a decision still needs to be made on whether or not it is the direction we want to take the project. Regardless, the extra time and resources it would take to implement OSGi in Rice for version 2.0 would prevent us from making this a reality. In the meantime, we will attempt to create reasonable modules for the existing codebase in order to position ourselves for whatever direction we may want to take modularity in the future.

Module Breakdown

Service Modules

  • rice-common
    • rice-common-api
    • rice-common-framework
    • rice-common-impl
    • rice-common-web
  • rice-ksb
    • rice-ksb-api
    • rice-ksb-framework
    • rice-ksb-impl
    • rice-ksb-web
  • rice-kew
    • rice-kew-api
    • rice-kew-framework
    • rice-kew-impl
    • rice-kew-web
  • rice-kim
    • rice-kim-api
    • rice-kim-framework
    • rice-kim-impl
    • rice-kim-web
  • rice-ken
    • rice-ken-api
    • rice-ken-framework
    • rice-ken-impl
    • rice-ken-web
  • rice-edl
    • rice-edl-api
    • rice-edl-framework
    • rice-edl-impl
    • rice-edl-web
  • rice-shareddata
    • rice-shareddata-api
    • rice-shareddata-framework
    • rice-shareddata-impl
    • rice-shareddata-web
  • rice-krms
    • rice-krms-api
    • rice-krms-framework
    • rice-krms-impl
    • rice-krms-web

Framework Modules

  • rice-core
    • rice-core-api
    • rice-core-impl
  • rice-krad
    • rice-krad-dd
    • rice-krad-bo
    • rice-krad-security
    • rice-krad-rules
    • rice-krad-web
    • rice-krad-document
    • rice-krad-lookup
    • rice-krad-inquiry
  • rice-devtools

Other Modules

  • rice-standalone
  • integration-tests
    • ksb
    • kew
    • ...
    • test-clients
      • ksb-test-client1
      • ksb-test-client2
      • ...
  • rice-tools

Package Naming Standards

Package names should reflect to the largest extent possible the module that they are in. Generally, this means they should follow a pattern like the following for the package prefix:

Each of the portions of the package name are defined as follows:

  • module - the module is the high-level module name for a particular component of Kuali Rice. The module itself does not have any code directly associated with it (in otherwords, no jar is produced), instead the module is divided into a series of sub-modules that contain code. Examples of Rice modules include: kew, kim, krad, ksb, etc.
  • sub-module - a sub-module is a smaller unit of a larger module. It contains code and resources that are compiled and assembled into a jar file. In general, a sub-module uses one of a standard set of orientations which determines it's role within the larger Kuali Rice stack as well as how it is invoked. Examples there are currently 4 different sub-module orientations defined in Kuali Rice:
    1. api
    2. framework
    3. impl
    4. web
  • domain - the domain represents the specific functional portion of the module which correspond to some logical domain. For example, in KIM this might include group, identity, role, etc. In certain cases classes may cross multiple domains in which case a "shared" package should be used. For framework modules like KRAD, they may skip the sub-module concept altogether and instead be packaged based on domain (i.e. document, uif, bo, dd, etc.)

As an example, the package prefixes for the various KIM modules would look like the following:

  • org.kuali.rice.kim.api.*
  • org.kuali.rice.kim.framework.*
  • org.kuali.rice.kim.impl.*
  • org.kuali.rice.kim.web.*

This portion of the package will generally correspond to the maven module. So in the above example, each of these package names would encompass the following maven modules:

  • rice-kim-api
  • rice-kim-framework
  • rice-kim-impl
  • rice-kim-web

As mentioned previously, within each of those modules it is expected that further non-Maven defined modularization of the module be handled via a further breakdown of package names based on domain.

For example, in KIM the following breakdown of domains makes sense:

  • identity
  • group
  • role
  • permission
  • responsibility
  • shared

In which case, KIM would be further broken down into packages as follows:

  • org.kuali.rice.kim.api.identity.*
  • org.kuali.rice.kim.api.group.*
  • org.kuali.rice.kim.api.role.*
  • org.kuali.rice.kim.api.permission.*
  • org.kuali.rice.kim.api.responsibility.*
  • org.kuali.rice.kim.api.shared.*

Finally, we should discontinue use of a "layer-based" approach to packaging our source code. Packaging according to layer instead of domain/feature is generally considered a bad practice [2].

Some of these that we have used previously which we should discontinue use of are as follows:

  • service
  • dto
  • dao
  • bo
  • etc...

Package structures that are nested deeper than the domain level should be used judiciously. Such cases where it deemed that is necessary should be considered carefully. One specific case where this is necessary is when packaging object that are tied to a specific version of Rice (such as dtos and services used for remoting). In these cases, underneath the domain level should be a package that include a version id, as follows:

The version id should start with the letter "v" and contain the major and minor version numbers separated by underscores, as in the the following examples:

  • v1_1
  • v1_2
  • v1_3
  • v2_0

Note that we do not need to include the patch version number as part of this version id because patch releases should not introduce any changes that affect version compatibility.

So, in KIM this might look like:

  • org.kuali.rice.kim.api.group.v1_1.*
  • org.kuali.rice.kim.api.identity.v1_1.*
  • etc.

Proposed Architecture

The following diagram shows a proposed architecture for the layering of the various sub-modules within Kuali Rice (based on the module breakdown provided previously).

Gliffy Zoom Zoom module-architecture

Here are a few important notes about this diagram:

  1. This diagram implies dependencies start from the top and working toward the bottom. So the web modules will have the most dependencies, with the core having the fewest dependencies.
  2. It is intended that none of the impl modules will have dependencies on any other impl module. So these should be fully isolated from each other.
  3. It is intended that none of the api modules, with the exception of core, will have dependencies on any other api module.
  4. The impl and web framework boxes are show at the same level because the web modules will depend on both of those, but impl and web framework should not be interdependent.
  5. The framework modules (at a specific level in the diagram) are permitted to be interdependent provided that there are no circular dependencies.

Client Application Usage of Kuali Rice code

Rules Pertaining to Usage

As a result of the remodularization, it will be easy for a client application developer to understand which portions of the codebase they are allowed to invoke directly.

The rules will be as follows:

A client application is allowed to invoke, implement, extend from, or otherwise utilize code in Kuali Rice only if it meets one of the following conditions:

  1. It is part of an api-oriented sub-module
  2. It is part of a framework-oriented sub-module

It will be easy to identify this code by package name because a standard will be in place as mentioned previously. To reiterate, the packages will be named as follows:

  • api-oriented sub-module - org.kuali.rice.<module>.<sub-module>.api.*
  • framework-oriented sub-module - org.kuali.rice.<module>.<sub-module>.framework.*

The following diagram is a simple depiction of this:

Gliffy Zoom Zoom client-app-invocation-rules

Service Customization

In certain cases, customization of services is required. This is common the case in modules like KIM where an implementor may need to override the default IdentityService implementation in order to integrate it with LDAP.

In these cases, the implementor might extend from classes in an implementation module (though it is not required to do so). If they do this, then there is not gaurantee for them that their custom extension of the service will stay compatible when they upgrade to the next version of Rice. It's likely that something may have changed in the impl class which would require them to make changes in their custom custom.

Targeted Architecture Changes in Support of Modularity

This list of changes is not comprehensive, but highlights some of the major areas that need to be addressed as part of this work.

Model Objects

Definition

Model Object is a generic term for a object that represents a piece of the domain in a given software system (a domain object).  These objects can come in mutable and immutable forms.  They can be simple transfer objects, orm mapped objects, remotable objects, etc.  In rice examples of model objects are: Business Objects (data mapped objects) and Data Transfer Objects (jaxb annotated remotable objects).

As a part of modularity and version compatibility we need to bring some consistency to our Model Objects.  This consistency will provide guidelines for future development, ensure api contracts are met, and help reduce coupling across rice components.

Requirements

  1. Model Object's returned from and passed to services should document and enforce their contracts
    1. the simplest way to do this is to make them immutable
  2. Model Object's should follow a standard design (ie implement Serializable), override toString
    1. Immutable Model Object's and jaxb ashould override equals, hashcode, and be marked final
  3. Model Objects should follow a standard naming convention (FooBo, FooDto)
  4. All Model Objects should implement a common interface to enforce a standard contract
  5. Rice apis should reference a simple/consistent type rather than BOs, DTOs
  6. DTOs can be versioned along with their services.
  7. DTOs would contain jaxb annotations
  8. BOs would be the only Model Objects to be orm mapped

Immutable Objects

Immutable objects are objects that cannot change once created.  This has many advantages including:

  • Simple from a state space perspective.  They can only have one state and it never changes
    • This makes validation & contract enforcement easy - they enforcement only has to happen on construction.
  • Thread-safe.  Once created they require no synchronization.  This makes them great for things like caches.
  • They can be used in other libraries safely (ie. keys in hashmaps)
  • They can be sent to/consumed by other code without fear of undesired modification.
  • Behave to the same in local api calls versus remote api calls

Some of the disadvantages include:

  • Does not work with some frameworks (kns, hibernate, jaxb) - so translation would be required
  • Complex immutable objects require a construction pattern (ex: Builders) which contains boilerplate code
  • Copy objects to make simple changes.  Structural sharing can be used as long as all objects in an object graph are immutable (which they have to be for a top level object to be immutable).

Open Points

  • How to enforce standards in Model Object design that cannot be enforced using java interfaces
    • Can we use a tool to enforce naming conventions, coupling, existence of static factory methods on BOs, DTOs, etc?
      • maybe use Lattix, CheckStyle, PMD, etc?
  • Can we make the boilerplate code less painfull?
    • Passive code generation?
    • AspectJ weaving?
      • more complex
      • much less boilerplate

Proposal

The rice team has come to the realization, that we have three distinct representations of a model object in rice.  Each representation is designed to work with certain parts of rice and serve different purposes.

BOs

BOs are orm-mapped, mutable, Serializable model objects.  They override toString. They are an implementation detail of rice and should not be referenced by client applications (other than through some requirement of the kns).  These object will need to be used by the maintenance screens of rice. Service APIs should not be written in terms of these objects except specific to the kns or at the DAO level. These objects always represent the most current version of the model object - they are not versioned.

DTOs

DTOs are jaxb annotated, mutable (although setters are not provided), Serializable, final model objects.  They override toString, equals, & hashcode.  These are only used by the remote APIs of rice.  They are not meant to be used by clients except where the client is a soap client not using the ksb.  Service APis should not be written in terms of these objects except in the internal remoting section of rice. These objects are versioned.

Concrete

Concrete model objects are immutable, final model objects.  hey override toString, equals, & hashcode. They are only constructed via a nested builder class to allow for optional fields.  These are the model objects that clients should reference.  All rice APIs should be written in terms of these objects when possible. These objects always represent the most current version of the model object - they are not versioned.

An example:

Using the example at the following location (for simplicity we'll just focus on the Entity objects):

http://test.kuali.org/svn/rice/vc-interface-proc-1-1/

elements of this example:

ModelBuilder: This class is the interface that all "Builder" classes implement. It's purpose is to make sure all of our builders conform to a standard API.

ModelObjectBasic: An interface that documents that "basic" model objects must be Serializable & have a overridden toString method. All model objects in rice will at least be a basic model object.

ModelObjectComplete: An interface that extends ModelObjectBasic which declares that "complete" model objects in rice will also override equals and hashcode along with what the super interface declares.

"Contract" classes: These classes define the contract for out model objects. They are not meant to be a type abstractions (at the service layer) but rather they ensure that all of our model object implementations stay consistent. They do allow for a mechanism to easily convert from different model object implementations as we'll go over later (in this regard they provide a necessary type abstraction). To put it another way, their use should be in a very limited fashion.

"Concrete" Model Object (ex: Entity) classes: These classes are an immutable implementations of a model object that have no suffix (like Bo, Dto). They implement ModelObjectComplete & the "Contract" interface. They are final classes and have private constructors. They can only be built by a nested "Builder" class. All "concrete" model object's MUST have a nested "Builder" because nested builders allow for optional fields among other things.

"Builder" classes: These are nested inside the classes that they build. They do field validation in their setters and optionally in their build method. They ensure that a "concrete" model object are always built in a valid state according to it's "Contract" class. They are final classes. The implement the "Contract" interface & ModelBuilder. The constructor is private and instead have two static factory methods which are overloaded called "create()". One factory method constructs a new builder from an existing model object. This essentially encapsulates copying of model objects in order to construct a new instance.

public Service interfaces (EntityService): These are standard rice services but are written using the "Concrete" Model Objects. They should not be written in terms of the "Contract" classes as that would allow passing/returning unsafe mutable versions of a model object

PersistableBusinessObject interface: This interface is for all persistable Business Objects. It extends ModelObjectBasic.

"Bo" classes: these are the orm mapped model objects (pojos). They implement the "Contract" interface & ModelObjectBasic. They are mutable and unsafe and should be confined to the data access layer unless required by the kns. They always have several static factory methods call to() & from(). These methods convert between "concrete" model objects & "Bo" model objects. These methods are very important for translation when working across service layers in rice.

"Dao" interfaces: These are standard rice Daos but are written using the "Bo" Model Objects. They should not be written in terms of the "Contract" classes as that would allow passing/returning a non-orm mapped objects which would not make sense.

public Service implementations (EntityServiceImpl): These are standard rice services but are written using the "Concrete" Model Objects. They may call into the "Dao" classes and therefore may have to translate parameters and return values using the "Builder" classes or static factory methods (to(), from()).

DtoVersionRoot class: Is the base class for all "DtoVersionRoot" classes. It implements ModelObjectComplete. It also defines FINAL implementations for toString, hashcode, and equals. It is also marked as XmlTransient with jaxb.

"DtoVersionRoot" classes: The base class for all "Dto" classes. They extend DtoVersionRoot & implement the "Contract" interface. They are also marked as XmlTransient with jaxb. They will provide default implementation for all methods that are introduced after 1.0 of a Dto. See Rice 2.0 DTO How-To

"Dto" classes: these are the jaxb annotated, remotable model objects. They extend DtoVersionRoot class. They are mutable and unsafe and should be confined to the remote layer. They do not have setters because they are not required by jaxb. They are also marked final since they should not be extended. They always have several static factory methods call to() & from(). These methods convert between "concrete" model objects & "Dto" model objects. These methods are very important for translation when working across service layers in rice. Theses artifacts are versioned.

"ServiceRemoteFacadeVersionRoot" classes: These classes are the base class for remote service facades. They will provide default implementation for all methods that are introduced after 1.0 of a service api. They implement the public Service interface.

"ServiceRemoteFacade" classes: These classes extend the "ServiceRemoteFacadeVersionRoot". They delegate to the "SoapService" and therefore have to translate parameters and return values using the "Builder" classes or static factory methods (to(), from()). These are what a remote client would end up calling when using a rice service. Theses artifacts are versioned.

"ServiceSoap" interfaces: interfaces that basically mirrors the "public Service interface" although they don't share a common interface - too bad! This interface is jax-ws annotated and is written in terms of the versioned Dtos. Theses artifacts are versioned.

"ServiceSoapEndpoint" classes: These classes implement the ServiceSoap. They delegate to the public Service interface and therefore have to translate parameters and return values using the "Builder" classes or static factory methods (to(), from()). These are what is called into remotely from a remote client on the server side. Theses artifacts are versioned.

Component-Level Analysis

Most of the work being done here so far is being done using the Lattix tool. There is a seperate document where detailed notes on this are being kept, and it can be found here:

Additionally, the link to the Lattix web repository for Kuali Rice is:

In the following sections, we will provide information related to different modules of Rice that have specific modularity projects that are being done which warranted additional documentation.

KNS and KRAD

Phased Modularity Approach

The KNS module is going to be one of the more difficult modularization projects because of the desired level of modular breakdown that is proposed in this document. In order to facilitate the work being done on KRAD for Rice 2.0, we are adopting a multi-phase approach to KNS/KRAD modularity.

The details on this phased approach can be found here: Rice 2.0 - KNS to KRAD Modularity Refactoring Plan

Notes Refactoring

As part of the KNS/KRAD modularization work, some refactoring needs to be done which relates to the notes and attachments framework.

Details on this refactoring plan can be found here: Rice 2.0 - KNS Notes Modularity Refactoring Proposal

Web Content modularity

We content generally includes the following set of artifacts:

  • CSS
  • JavaScript
  • images (gif, jpg, png, etc.)
  • static html
  • Java Server Pages (JSP)
  • tag libraries
  • tag library descriptors (tld)
  • web.xml
  • struts-config.xml

Furthermore, there are a few different types of web content that Rice provides:

  1. Web Framework Modules
  2. Server Web Modules
  3. Client Web Modules

Web Framework Modules - this is essentially KRAD and includes web framework pieces including the JSP pages needed for KRAD-based applications, standard Kuali CSS, KRAD tag libraries, and javascript. This also includes the Rice portal framework which can be included to build simple portal functionality in each client application if desired.

Server Web Modules - these modules include the different web modules for the various Rice components (i.e. KEW, KIM, etc.). It is intended that client applications will not load these modules.

Client Web Modules - there are certain web modules which contain some user interface functionality that a client application might want to load to provide some administrative functions against client-side Rice data. This primarily includes KSB screens that can be used to maintain an application's thread pool and message queue. It would also components like the "configuration viewer" from the Rice core.

As of Rice 1.0.x, all of the content listed above is included in a web module of the project and it includes content from each of the individual modules of Rice.

For Rice 2.0, the different web modules will be separated out as distinct maven modules. Each of these will use a war packaging type in their pom. This will produce a war from each of these which will be deployed to the maven repository and can be used in overlays. Ultimately, this separation of modules in this fashion should facilitate deployment models where instances of different services could be deployed independently for flexibility and scalability purposes. However, this is not a requirement of web content modularity for Rice 2.0.

The layout of these different web modules from a dependency perspective would look as follows:

Gliffy Zoom Zoom rice-web-modularity-1.1

A web content module can depend upon another module and will effectively perform a maven overlay of content from the module(s) it depends on.

Impact Analysis

The modularity and version compatibility projects are setting the Kuali Rice project up for a sustainable future which will provide as much compatibility and ease of migration across the different versions of Rice. Based on the current Rice roadmap, this will likely take us to some time in 2014 before we would be able to create a Kuali Rice 2.0. To this end, in order to accomplish our goals and best position the project, the work we are doing on both modularity and version compatibility will introduce a significant level of impact. General work that will be happening across the codebase during this release that could contribute to this includes (but is not limited to):

  1. Changing package names to align them with packaging standards as defined in this document.
  2. Changing service names to align them with standardized conventions, this is important because the service name a service is provided under forms part of the contract between client and server.
  3. Changes to database table names for classes which might be moving between modules.
  4. Changes to services and their public API methods to react to api changes that might need to happen in order to improve or cleanup the apis.
  5. Introduction of generic type usage for classes that were not previously taking proper advantage of generics.
  6. Changes to classes that are designed for extension (i.e. DocumentBase and others) to improve the ability of the project to best manage compatibility of client
  7. Changes to how Rice is configured and how the lifecycle for the different Rice modules is managed.
  8. Changes to configuration parameters to align them with naming conventions and standards.
  9. Restructuring of how web content (jsps, tags, css, javascript, images, etc.) are packaged and delivered for inclusion in client projects.
  10. Removal of deprecated methods and classes.
  11. Upgrading of 3rd party libraries to the latest version.

The Rice team is attempting to take notes for all such changes which are occurring during this release. The information can be found in the documents linked below:

References

  • No labels

6 Comments

  1. Regarding the "client apps do refer to rice DD entries - what do we do about this?" comment.

    We can create abstract bean definitions which do not ultimately reference the DD implementation classes. (Like "PersonImpl-principalName") Until the beans are finally resolved, Spring holds everything as Map<String,String>. So, we could provide beans with the same name as those which are presently being used by client applications in the API modules. The impl modules could then inherit from those to build their concrete instances of DD objects. Client applications do not (AFAIK) extend/reference the BusinessObjectEntry objects themselves.

    1. That's a good suggestion Jonathan, i'll need to think through if that will do what we need but I think it might work. Thanks!

  2. I was also hoping that, as part of the refactoring, that the static helper classes like "ObjectUtils/FieldUtils/etc..." could be removed and their logic transferred to services. When an issue is found in one of these, it is impossible to fix without copying the entire class into your own code base, rather than extending and fixing the single issue.

    Probably, though, knowing how ingrained and widespread some of these are in KFS, they would have to be deprecated, with their logic moved into a service, but retaining the static helper class as a proxy until client code can be refactored.

    1. We have actually discussed this some. Our stance on this is that anything which is part of a true "utility" class should be something that you would never need to override or customize. You might be referring to bugs here though, and to that end I would probably say that if a bug is found then the class should probably just be "patched" by the implementer and that patch should be contributed back to the project for inclusion.

      Saying that though, we definately noticed that we have way too much code in utility classes right now which does not fall into the category of simple utilities that I described previously (KimCommonUtils is an example). To this end we are going to establish a pattern of either breaking up monolithic utility files and/or pushing some of the logic down behind the service layer. It will most likely be something that is done on a case-by-case basis, but we've already identified the problematic utility classes in our modularity analysis notes.

      1. Regarding this:

        > if a bug is found then the class should probably just be "patched" by the implementer and that patch should be contributed back to the project for inclusion

        Yes - I agree, but not everyone will, necessarily. What worries me is that it puts implementors in the position of replacing the entire class and then being more subject to encountering problems when they upgrade if they forget to replace that class, as the newer version may have changed APIs/functionality.

        Whereas, if they have only manipulated that one method in a subclass, the potential impact is less during upgrades.

  3. Regarding:

    BOs are orm-mapped, mutable, Serializable model objects. They override toString. They are an implementation detail of rice and should not be referenced by client applications (other than through some requirement of the kns). These object will need to be used by the maintenance screens of rice. Service APIs should not be written in terms of these objects except specific to the kns or at the DAO level. These objects always represent the most current version of the model object - they are not versioned.

    I think that BOs should be allowed to be persisted in multiple ways, not just via the ORM tool (unless Rice itself becomes that ORM tool and proxies to an implemention like JPA or OJB.) One feature which should be made possible is to use the KNS framework for Documents, Lookups, and Inquiries on objects which are not loaded from the database. For example, an object may be stored in an external system, but a UI may be provided within the Rice client application for lookups or modification. Technically, this is possible now (best example would be some of the KIM documents and the KFS extensions of them.) But, it requires a lot of fiddling with the framework to get it to accept the proxy business objects.

    See some of the notes on the page linked below. Some of it may need to be reviewed in light of the separation of DTO/BO objects, but those concepts do not provide for the use of the KNS UI infrastructure by non-BO classes.

    https://wiki.kuali.org/display/KULRICE/KNS+Business+Object+Refactoring