Kelven,
(I apologize in advance for the length of my reply.)
I agree completely that we need to consider adopting a third-party dependency
injection framework. I propose that we give strong consideration to Spring
because of the following additional capabilities it provides:
Authentication and Authorization: Spring Security provides robust integration
with SSO facilities (CAS by default) to support enterprise authentication
scenarios (including LDAP and AD). It also provides a powerful authorization
capability to implement RBAC controls.
Configuration: Resolves configuration options from a hierarchy of sources and
injects their values through Spring DI. It also provides the means to bind
dependencies in code at runtime -- preventing a proliferation of annotations
across the codebase.
Transaction Management and JDBC Framework: A powerful, annotation based
transaction management facility with a nice set of abstractions for using JDBC
in a robust manner.
Scheduling: Spring provides a nice set of abstractions around various Java
scheduling mechanisms including Quartz.
Currently, CloudStack only authorizes users at the endpoint. Furthermore, the
authentication sessions are not shared in a manner that would permit
integration with other enterprise facilities (e.g. storage, LDAP, management
tools, etc). Shifting to Spring Security would permit fine-grained
authorization at the service boundary, and allow CloudStack to share
authentication session information in a trusted manner via third-party SSO
services. Finally, CloudStack would be able to defer LDAP/AD integration and
password hashing strategies to the SSO services while allowing more advanced
security features such as multi-factor authentication.
As discussed elsewhere on the list, the myriad of technologies integrated by
CloudStack introduces many opportunities for conflicting dependencies (i.e. JAR
hell), as well as, a requirement to isolate components. As such, CloudStack
seems like a good candidate for a component framework such as OSGi. While the
transition would require a lot of work (e.g. making the CloudStack artifacts
and third-party dependencies compliant, enhancing the build process, etc),
Spring was one of the original adopters of OSGi, and the dependency injection
framework works well with it.
I find the Spring XML configuration to be a horrendous blight on a codebase
followed closely by the proliferation of @Inject/@Component annotations
everywhere. In addition to tightly coupling the codebase to a dependency
injection framework (JSR-333 annotations do little to help matters, IMHO), they
require test frameworks on top of frameworks to perform unit testing. The most
valuable unit test suites are well-isolated and simple. The additional
overhead of these test frameworks adds unnecessary complexity to test case
authoring and debugging activities. Thankfully, Spring Configuration provides
the means to implement dependency injection without XML or annotations
everywhere. Borrowing the UsageManager example below, every subsystem (e.g.
usage) provides a single class annotated with the @Configuration annotation
such as the following:
com.cloud.usage;
@Configuration
public class UsageSubsystem {
@Component
private AccoutDao m_accountDao
// Inject other dependencies required by services exposed by this subsystem
// Inject configuration options required by services exposed by this subsystem
@Component
public UsageManager usageManager() {
return new UsageManagerImpl(m_accountDao, /* other dependencies and
configuration options */
}
// Method per service implementation binding
}
Since the dependency injection framework is isolated in this Subsystem class,
UsageManagerImpl is simplified to the following:
com.cloud.usage;
final class UsageManagerImpl implements UsageManager {
private final AccountDao m_accountDao;
// Other dependencies …
UsageManagerImpl(AccountDao accountDao, /* other dependencies and configuration
options */) {
super();
this.m_accountDao = accountDao;
}
// UsageManager implementation
}
In this model, unit tests do not need to concern themselves with DI framework.
They create mock implementations of each UsageManagerImpl dependency and create
an instance using the new operator. Also, Spring provides a well-defined
lifecycle for configuration classes, including startup and shutdown hook
methods when the Lifecycle interface is implemented, and dependency graphs
(e.g. Subsystem C depends on the A and B). Finally, the dependencies on the DI
framework are isolated to a handful "subsystem" classes.
Currently, CloudStack maintains its own infrastructure for transaction
management and JDBC infrastructure code. Spring provides a robust transaction
management mechanism exposed via annotations and JDBC templates that would us
to remove code under maintenance while adopting facilities that are well
understood and widely used across the industry.
Finally, there are number of places in the CloudStack code based with scheduled
tasks occur. Each capability is implemented independently (usually using a
ScheduledTaskExecutor) with no coordination. Furthermore, none of these
scheduled tasks a standard approach for crash recovery or retry. Adopting a
centralized scheduling framework would not only establish consistency for these
types of operations, but also allow CloudStack to leverage error recovery and
retry provided by those facilities.
I am sure I have left out a myriad of details. I believe that we can simplify
the codebase while increasing capability through a framework such as Spring. I
also believe that adopting mechanisms for these needs that are widely used
through our industry will make the codebase more approachable to potential
contributors while leaving the project to focus on its core cloud management
capabilities.
Thank you for sticking with my long stream of consciousness reply,
-John
On Nov 8, 2012, at 7:19 PM, Kelven Yang <[email protected]> wrote:
> As part of architecture refactoring work, we are exploring some
> initiatives that would help CloudStack move toward more component-based
> architecture, one of such efforts is to standardize the way on how a
> single individual java component to be integrated into CloudStack system.
>
> CloudStack currently uses ComponentLocator to bootstrap components, it has
> done a great job while in the mean time, there are a couple of issues with
> this customized component injection framework (in my view).
>
> 1) ComponentLocator can not fully perform dependency injection, it can
> inject independent components without a problem, however, when things come
> with dependency, it will become tricky, although most of time auto-wiring
> with @Inject annotation works, but in some other time, developers will
> have to use following explicit wiring logic to make things work.
>
> @Local(value={UsageManager.class})
> public class UsageManagerImpl implements UsageManager, Runnable {
> public static final Logger s_logger =
> Logger.getLogger(UsageManagerImpl.class.getName());
>
> protected static final String DAILY = "DAILY";
> protected static final String WEEKLY = "WEEKLY";
> protected static final String MONTHLY = "MONTHLY";
>
> private static final int HOURLY_TIME = 60;
> private static final int DAILY_TIME = 60 * 24;
> private static final int THREE_DAYS_IN_MINUTES = 60 * 24 * 3;
> private static final int USAGE_AGGREGATION_RANGE_MIN = 10;
>
> --> hard-wiring logic goes here...
> private final ComponentLocator _locator =
> ComponentLocator.getLocator(UsageServer.Name, "usage-components.xml",
> "log4j-cloud_usage");
> private final AccountDao m_accountDao =
> _locator.getDao(AccountDao.class);
> private final UserStatisticsDao m_userStatsDao =
> _locator.getDao(UserStatisticsDao.class);
> private final UsageDao m_usageDao = _locator.getDao(UsageDao.class);
> private final UsageVMInstanceDao m_usageInstanceDao =
> _locator.getDao(UsageVMInstanceDao.class);
> private final UsageIPAddressDao m_usageIPAddressDao =
> _locator.getDao(UsageIPAddressDao.class);
> private final UsageNetworkDao m_usageNetworkDao =
> _locator.getDao(UsageNetworkDao.class);
> private final UsageVolumeDao m_usageVolumeDao =
> _locator.getDao(UsageVolumeDao.class);
> private final UsageStorageDao m_usageStorageDao =
> _locator.getDao(UsageStorageDao.class);
> private final UsageLoadBalancerPolicyDao m_usageLoadBalancerPolicyDao
> = _locator.getDao(UsageLoadBalancerPolicyDao.class);
> private final UsagePortForwardingRuleDao m_usagePortForwardingRuleDao
> = _locator.getDao(UsagePortForwardingRuleDao.class);
> private final UsageNetworkOfferingDao m_usageNetworkOfferingDao =
> _locator.getDao(UsageNetworkOfferingDao.class);
> private final UsageVPNUserDao m_usageVPNUserDao =
> _locator.getDao(UsageVPNUserDao.class);
> private final UsageSecurityGroupDao m_usageSecurityGroupDao =
> _locator.getDao(UsageSecurityGroupDao.class);
> private final UsageJobDao m_usageJobDao =
> _locator.getDao(UsageJobDao.class);
>
> The problem of doing so is that we create a cross-reference between
> component and the component container, it breaks the principle of being
> component-ized. In the above case, the component shouldn't really make any
> assumption of its container, and the other problem is that developers will
> sometimes be confused at when, under what situation, they can use @Inject
> to wire a component or they have to use run-time wiring logic like above
> to make it work.
>
>
> 2) AOP pattern is implicitly buried with current injection framework.
>
> AOP(Aspect Oriented Pattern) is very powerful concept, and CloudStack uses
> it a lot, @DB annotation is a very typical example. However, wiring an
> aspect is not that explicit, it buries inside those interceptors that work
> together with ComponentLocator during injection phase. Although AOP
> concept is powerful, but it better to be explicit, we need to encourage to
> component-ize the AOP aspect itself as component, although
> ComponentLocator has no problem to offer AOP support, it lacks of letting
> people do it in a more explicit way.
>
> 3) Unit test integration
> In the example in 1), to perform a unit test on the component, it involves
> with having to construct a mocked ComponentLocator, these little pieces
> add up to make unit-testing a little challenge in CloudStack.
>
> 4) CloudStack specific logic
>
> ComponentLocator also has CloudStack specific assumptions on Adapters,
> Managers, PluggableService etc, this has to move up to the business layer
> and leave this underlying layer generic and clean, if in the future people
> want to add some other cool stuff like CoolServiceGates etc, it requires
> the developer to modify ComponentLocator, this kind of possibility alone
> makes ComponentLocator itself not really a component, we should not
> encourage that.
>
> As a solution, we are exploring the possibility to adopt Spring Framework
> into CloudStack, it has the ability to address all above issues, the goal
> here is to leave all these generic concerns away from CloudStack core
> business logic, and standardize the way we program in CloudStack. The goal
> is
>
> 1) Individual component should be clean, no hard-coded assumption of any
> component container framework
> 2) System to in layered with clean boundary. No business logic leaked into
> generic plumbing layer.
> 3) AOP pattern to be explicit, represented through aspect component and
> explicit wiring of the AOP
> 4) Make unit test simple, simple and simple (Junit Spring integration has
> already done that)
>
> Since 4.0 is already out of the door, I re-raise the discussion here
> again, please feel free to share your opinions
>
> Kelven
>
>
> On 10/19/12 6:14 PM, "Kelven Yang" <[email protected]> wrote:
>
>> David,
>>
>> Thanks for the remind. As we are working on a feature branch(Javelin) and
>> thought not to heat-up the mailing list while everyone is still busy on
>> getting the 4.0 release out of the door.
>>
>> We are evaluating the possibility to leverage Spring Framework to
>> standardize Dependency injection/AOP pattern usages in CloudStack. Spring
>> framework is very popular to Java community and under Apache license. By
>> switching the underlying architecture plumbing to a de-facto standardized
>> framework would help attract developers to focus more on CloudStack core
>> business logic with already familiar coding practices.
>>
>> We will not extensively use full featured Spring Framework, may primarily
>> on core DI/AOP features since these are commonly used in CloudStack, we
>> also haven't decided to go with Spring annotation based approach or XML
>> based approach yet. With currently check-ins, they are annotation based
>> which I personally prefer, however, this may change, depends on how to get
>> our old code loaded nicely under Spring.
>>
>> If anyone does have a strong opinion on this, please feel free to share
>> with us.
>>
>>
>> Kelven
>>
>> On 10/19/12 3:47 PM, "David Nalley" <[email protected]> wrote:
>>
>>> This adds a number of new dependencies.
>>>
>>> This needs to explicitly be discussed before it gets committed.
>>> See below for reference:
>>> http://markmail.org/message/huevw4ur73a64b5c
>>>
>>> On Fri, Oct 19, 2012 at 6:25 PM, <[email protected]> wrote:
>>>> Updated Branches:
>>>> refs/heads/javelin a75916d45 -> 8ef9e32cf
>>>>
>>>>
>>>> Architecture refactoring - Stateless management server - Spring
>>>> Framework initiatives
>>>>
>>>>
>>>> Project:
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/repo
>>>> Commit:
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/commit/8ef9e
>>>> 3
>>>> 2c
>>>> Tree:
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/tree/8ef9e32
>>>> c
>>>> Diff:
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/diff/8ef9e32
>>>> c
>>>>
>>>> Branch: refs/heads/javelin
>>>> Commit: 8ef9e32cfda4b1ce138ac167caa26521bd7a3508
>>>> Parents: a75916d
>>>> Author: Kelven Yang <[email protected]>
>>>> Authored: Fri Oct 19 15:24:01 2012 -0700
>>>> Committer: Kelven Yang <[email protected]>
>>>> Committed: Fri Oct 19 15:24:15 2012 -0700
>>>>
>>>> ----------------------------------------------------------------------
>>>> client/WEB-INF/web.xml | 9 ++-
>>>> pom.xml | 81
>>>> +++++++++++++++
>>>> utils/conf/db.properties | 1 +
>>>> utils/src/com/cloud/utils/events/EventBus.java | 4 +-
>>>> utils/src/com/cloud/utils/events/EventBusBase.java | 5 +-
>>>> utils/src/com/cloud/utils/events/Subscriber.java | 4 +-
>>>> 6 files changed, 99 insertions(+), 5 deletions(-)
>>>> ----------------------------------------------------------------------
>>>>
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /client/WEB-INF/web.xml
>>>> ----------------------------------------------------------------------
>>>> diff --git a/client/WEB-INF/web.xml b/client/WEB-INF/web.xml
>>>> index 50f2455..c6fd30f 100644
>>>> --- a/client/WEB-INF/web.xml
>>>> +++ b/client/WEB-INF/web.xml
>>>> @@ -20,7 +20,14 @@
>>>> xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
>>>> http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
>>>> version="2.5">
>>>>
>>>> -
>>>> + <listener>
>>>> +
>>>> <listener-class>org.springframework.web.context.ContextLoaderListener</l
>>>> i
>>>> stener-class>
>>>> + </listener>
>>>> + <context-param>
>>>> + <param-name>contextConfigLocation</param-name>
>>>> + <param-value>classpath:applicationContext.xml</param-value>
>>>> + </context-param>
>>>> +
>>>> <servlet>
>>>> <servlet-name>cloudStartupServlet</servlet-name>
>>>>
>>>> <servlet-class>com.cloud.servlet.CloudStartupServlet</servlet-class>
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /pom.xml
>>>> ----------------------------------------------------------------------
>>>> diff --git a/pom.xml b/pom.xml
>>>> index 3c14d4e..5f63ce6 100644
>>>> --- a/pom.xml
>>>> +++ b/pom.xml
>>>> @@ -79,6 +79,7 @@
>>>> <cs.servlet.version>2.4</cs.servlet.version>
>>>> <cs.jstl.version>1.2</cs.jstl.version>
>>>>
>>>> <cs.selenium.server.version>1.0-20081010.060147</cs.selenium.server.vers
>>>> i
>>>> on>
>>>> +
>>>> <org.springframework.version>3.0.5.RELEASE</org.springframework.version>
>>>> <skipTests>true</skipTests>
>>>>
>>>> </properties>
>>>> @@ -169,6 +170,86 @@
>>>> <version>${cs.junit.version}</version>
>>>> <scope>test</scope>
>>>> </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-core</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-expression</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-beans</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-aop</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-context</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-context-support</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-tx</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-jdbc</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-orm</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-oxm</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-web</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-webmvc</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + </dependency>
>>>> +
>>>> + <dependency>
>>>> + <groupId>org.springframework</groupId>
>>>> + <artifactId>spring-test</artifactId>
>>>> + <version>${org.springframework.version}</version>
>>>> + <scope>test</scope>
>>>> + </dependency>
>>>> +
>>>> </dependencies>
>>>>
>>>> <build>
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /utils/conf/db.properties
>>>> ----------------------------------------------------------------------
>>>> diff --git a/utils/conf/db.properties b/utils/conf/db.properties
>>>> index 6bdb6d6..8d98119 100644
>>>> --- a/utils/conf/db.properties
>>>> +++ b/utils/conf/db.properties
>>>> @@ -24,6 +24,7 @@ cluster.servlet.port=9090
>>>> # CloudStack database settings
>>>> db.cloud.username=cloud
>>>> db.cloud.password=cloud
>>>> +db.root.password=
>>>> db.cloud.host=localhost
>>>> db.cloud.port=3306
>>>> db.cloud.name=cloud
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /utils/src/com/cloud/utils/events/EventBus.java
>>>> ----------------------------------------------------------------------
>>>> diff --git a/utils/src/com/cloud/utils/events/EventBus.java
>>>> b/utils/src/com/cloud/utils/events/EventBus.java
>>>> index 4195acd..c1b6f70 100644
>>>> --- a/utils/src/com/cloud/utils/events/EventBus.java
>>>> +++ b/utils/src/com/cloud/utils/events/EventBus.java
>>>> @@ -17,9 +17,11 @@
>>>>
>>>> package com.cloud.utils.events;
>>>>
>>>> +import java.io.Serializable;
>>>> +
>>>> public interface EventBus {
>>>> void subscribe(String subject, Subscriber subscriber);
>>>> void unsubscribe(String subject, Subscriber subscriber);
>>>>
>>>> - void publish(String subject, PublishScope scope, Object sender,
>>>> String args);
>>>> + void publish(String subject, PublishScope scope, Object sender,
>>>> Serializable args);
>>>> }
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /utils/src/com/cloud/utils/events/EventBusBase.java
>>>> ----------------------------------------------------------------------
>>>> diff --git a/utils/src/com/cloud/utils/events/EventBusBase.java
>>>> b/utils/src/com/cloud/utils/events/EventBusBase.java
>>>> index 0c135db..cd10f1d 100644
>>>> --- a/utils/src/com/cloud/utils/events/EventBusBase.java
>>>> +++ b/utils/src/com/cloud/utils/events/EventBusBase.java
>>>> @@ -17,6 +17,7 @@
>>>>
>>>> package com.cloud.utils.events;
>>>>
>>>> +import java.io.Serializable;
>>>> import java.util.ArrayList;
>>>> import java.util.HashMap;
>>>> import java.util.List;
>>>> @@ -72,7 +73,7 @@ public class EventBusBase implements EventBus {
>>>>
>>>> @Override
>>>> public void publish(String subject, PublishScope scope, Object
>>>> sender,
>>>> - String args) {
>>>> + Serializable args) {
>>>>
>>>> if(_gate.enter(true)) {
>>>>
>>>> @@ -283,7 +284,7 @@ public class EventBusBase implements EventBus {
>>>> _children.put(key, childNode);
>>>> }
>>>>
>>>> - public void notifySubscribers(String subject, Object
>>>> sender, String args) {
>>>> + public void notifySubscribers(String subject, Object
>>>> sender, Serializable args) {
>>>> for(Subscriber subscriber : _subscribers) {
>>>> subscriber.onPublishEvent(subject,
>>>> sender, args);
>>>> }
>>>>
>>>>
>>>> http://git-wip-us.apache.org/repos/asf/incubator-cloudstack/blob/8ef9e32
>>>> c
>>>> /utils/src/com/cloud/utils/events/Subscriber.java
>>>> ----------------------------------------------------------------------
>>>> diff --git a/utils/src/com/cloud/utils/events/Subscriber.java
>>>> b/utils/src/com/cloud/utils/events/Subscriber.java
>>>> index 7af283b..c3baa6f 100644
>>>> --- a/utils/src/com/cloud/utils/events/Subscriber.java
>>>> +++ b/utils/src/com/cloud/utils/events/Subscriber.java
>>>> @@ -17,6 +17,8 @@
>>>>
>>>> package com.cloud.utils.events;
>>>>
>>>> +import java.io.Serializable;
>>>> +
>>>> public interface Subscriber {
>>>> - void onPublishEvent(String subject, Object sender, String
>>>> args);
>>>> + void onPublishEvent(String subject, Object sender, Serializable
>>>> args);
>>>> }
>>>>
>>
>