The road to Salamanca

To content | To menu | To search

Tag - Data Activities

Entries feed - Comments feed

Thursday, October 9 2008

QuickStart : the Activity

Whereas Data Access could be considered as yet another solution to a very common problem (the ORM problem), Data Activities is quite original in its conception and implementation. The concepts involved here are much less refined or complex, but have already proved to be very powerful.

FindProductListByNameFirst of all, we have to define an activity in terms of questions and treatments. The simplest activity I could imagine is the following :

  • this activity is about getting a list of products that match a specified name.
  • the first state is a question that asks for the name.
  • the second (and last) state is a treatment that finds the corresponding states.
  • let's add to this a rule that specifies that the name we are looking for should not be empty (nor null).

For this activity, we'll reuse the Product class we have defined in the Domain Model quick start. The proper way to implement this would be to add a FindByName method to this class and query the database, but we'll do it the Activity way this time.

An activity manipulates data. We need a class to store :

  • the name of the Product.
  • the list of products matching that name.

There is a base class in the Salamanca.DataActivities that will help us implement the IActivityData interface :

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Salamanca.DataActivities;

namespace QuickStart
{
public class Data:
ActivityData
{
private struct Backup
{
public string Name;
public Product[] Products;
}

protected override object CreateBackup()
{
Backup ret=new Backup();
ret.Name=_Name;
ret.Products=_Products.ToArray();
return ret;
}

protected override void RestoreBackup(object backup)
{
Backup b=(Backup)backup;
_Name=b.Name;
_Products=new List<Product>(b.Products);
}

public string Name
{
get { return _Name; }
set { _Name=value; }
}

public IList<Product> Products
{
get { return _Products; }
set { _Products=new List<Product>(value); }
}

private string _Name;
private List<Product> _Products=new List<Product>();
}

}

You see there is a bit more than just having a Name and a Products property. When inheriting from ActivityData, you have to implement two abstract methods that create and restore backups on the data. We won't really need this system in this activity, but data must implement IBackupable and this is quite straightforward as you can see in the code above.

Our activity will ask a question. This question is going to be implemented in the UI layer (likely via a TextBox on Windows Forms), so we need a way to allow it to plug itself into our activity. We do that via a question factory, as follows :

using System;
using Salamanca.DataActivities;
using Salamanca.DataActivities.UI;

namespace QuickStart
{
public abstract class QuestionFactory
{
public abstract Question AskProductName(IActivityController controller);
}
}

Our question state will need this QuestionFactory (or, rather, an implementation of it). And I anticipate that our treatment state will need a DataMapperCollection as we will retrieve all the products from the database via the Product.FindAll method. These are the dependencies of our activities, and we will group them in a parameter class :

using System;
using Salamanca.DataAccess.Collections;
using Salamanca.DataActivities;

namespace QuickStart
{
public class Parameters:
IQuestionFactoryParameters<QuestionFactory>
{
public Parameters(QuestionFactory factory, DataMapperCollection dataMappers)
{
_QuestionFactory=factory;
_DataMappers=dataMappers;
}

public DataMapperCollection DataMappers
{
get
{
return _DataMappers;
}
}

public QuestionFactory QuestionFactory
{
get
{
return _QuestionFactory;
}
}

private DataMapperCollection _DataMappers;
private QuestionFactory _QuestionFactory;
}
}

We are now ready to create our two states. The first one must implement IActivityInitialState, which is just a marker interface. As a question state, we can simply derive it from the QuestionActivityState base class. The second state just fills our Data with a list of products matching the name that was returned by the question :

using System;
using System.Collections.Generic;
using Salamanca.DataActivities;
using Salamanca.DataActivities.UI;
using Salamanca.DataRules;

namespace QuickStart
{
public class FindProductListByName:
QuestionActivityState<Data>,
IActivityInitialState
{
public FindProductListByName(Data data, QuestionFactory factory, DataMapperCollection dataMappers):
base(data, new Parameters(factory, dataMappers))
{
}

protected override IList<IRule> CreateRules()
{
IList<IRule> ret=base.CreateRules();
ret.Add(
new PredicateRule<FindProductListByName>(
"The name must not be empty.",
new Predicate<FindProductListByName>(
delegate(FindProductListByName s) {
return !string.IsNullOrEmpty(s.Data.Name);
}
)
)
);

return ret;
}

protected override void OnInitialized(ActivityStateEventArgs e)
{
Question=((Parameters)Parameters).QuestionFactory.AskProductName(e.Controller);
base.OnInitialized(e);
}

protected override IActivityState NextState
{
get { return new FindProductListByNameFindProducts(Data, (Parameters)Parameters); }
}
}

internal class FindProductListByNameFindProducts:
ActivityState<Data>
{
public FindProductListByNameFindProducts(Data data, Parameters parameters) :
base(data, parameters)
{
}

protected override ActivityStateResult Handle(IActivityController controller)
{
List<Product> allProducts=new List<Product>(Product.FindAll(((Parameters)Parameters).DataMappers));
Data.Products=allProducts.FindAll(
new Predicate<Product>(
delegate(Product p) {
return p.Name.Contains(Data.Name);
}
)
);

return ActivityStateResult.Next;
}

protected override IActivityState NextState
{
get { return new EndActivityState<Data>(Data); }
}
}
}

Note in the question state how we enforced our business rule (the name must not be empty).

Our activity is ready. Or is it ? What we have here is still an abstraction, and we need an implementation for our QuestionFactory. And that is precisely the magic of it all : we just need a concrete QuestionFactory.

Say, for instance, that we want to test our activity : we need a question factory that automatically provides the Data with test product names and checks the results. Here is an implementation of this question factory :

internal sealed class TestQuestionFactory:
QuestionFactory
{
public TestQuestionFactory(string name)
{
_Name=name;
}

public override Question AskProductName(IActivityController controller)
{
Question ret=new AnsweredQuestion();
ret.Answering+=new EventHandler<AnswerEventArgs>(
delegate(object sender, AnswerEventArgs e) {
((Data)e.Data).Name=_Name;
}
);

return ret;
}

private string _Name;
}

Based on this factory, a test would look like this (there are two products containing Queso in their name in the Northwind database) :

[TestMethod]
public void CanFindProductList()
{
Data data=new Data();
ActivityController controller=new ActivityController(
new FindProductListByName(
data,
new TestQuestionFactory("Queso"),
DataMappers
)
);
controller.Execute();

Assert.IsTrue(controller.HasCompleted);
Assert.AreEqual(2, data.Products.Count);
}

You can see here how an ActivityController is used to execute an activity. As all answers are automatically provided, our activity is executed from the beginning to the end in one phase. In a Windows Forms application, the execution would stop at the question state to let the user fill a text box in. When a Find button would be clicked, the controller would be told to resume the activity execution.

You can find a complete working example, as a Visual Studio 2008 solution, here. The same activity is implemented as a unit test, and as a Windows Forms application (the underlying database is Northwind on SQL Server, for which a data file is included in the downloadable sample).

Monday, September 8 2008

Inside the libraries (III) : Data Activities

Data Activities defines the base classes that help us implement our architectural pattern, centered around business activities. All the concepts developed in this context owe a lot to the vision and the ideas of Yves Darmaillac.

We define an activity as a part of a business process. All the business process parts that are involved in a business application define this application activities. An activity is a , comprising two kinds of states :

  • treatment states : those require no external data (think user interaction). For instance, a treatment state can perform an action on our domain model (create a new reservation), or directly on the data (calculate booking fees). All a treatment state needs to perform its action is data.
  • question states : those do require external data. For instance, a question state can ask for data for a domain model instance (fill reservation details), or it can ask for a specific domain model instance (pick up your seat). Usually, a question is answered via user interaction (but not necessarily, like in the case of unit tests where answers can be automated).

The golden rule here is to make sure that all the application behavior is defined in terms of activities. Treatments are by definition self-sufficient and generic. Questions define abstractions that have to be implemented by the HMI layer. In effect, such abstractions can be implemented by all kinds of presentation technologies (console applications, Windows Forms, ASP .NET, WPF...) with no impact on the business logic.

BasicsThe fundamental interfaces defined in this library are :

  • IActivityState is an interface implemented by an activity state, and treatment states in particular. IQuestionActivityState is a specialization that is implemented by a question state.
  • IActivityData is implemented by an activity data holder type : it holds the data that is manipulated by a specific activity.
  • IActivityParameters is implemented by an activity parameters holder type. An activity parameter could be seen as an immutable activity data.
  • IActivityController is implemented by an activity controller. Such a class is essential to the activity execution : it initiates the activity, terminates it and controls all the state transitions in between.

All the states must be validated before transition : this is achieved via the integration of Data Rules. The library defines base implementations for all these interfaces, and helpful classes to implement questions in specific presentation technologies (so far Windows Forms and ASP .NET).

Activities are at the core of our architecture : they manipulate data and act as a separation of concerns between the business logic and the presentation. In the future, we will be able to design our activities via our own DSL (SAML), and have base implementations (and best practices) for questions in other technologies such as WPF or ASP .NET AJAX.

I would like to finish with an analogy that might confuse some readers, but hopefully enlighten others : imageI like to think of a business activity as a RNA translation process, which is part of the protein biosynthesis. The elements involved in this process are :

  • a mRNA template. It holds the code for a specific protein. This code has a start and an end, and has to be followed in order. This is our finite state machine, each codon being seen as a state.
  • a ribosome. This is the protein "factory". It "reads" the codons one by one and performs an action on each of them : add the corresponding amino acid to the chain that will become our protein. This is our activity controller.
  • the protein. This is the result of the entire process. This could be seen as our data.

Wednesday, August 20 2008

Visiting Salamanca

Fundamentally, Salamanca is designed around three sets of libraries :Architecture

  1. Data Access is the easiest to explain in a few words : think of it as (yet) an (other) (ORM) library. Why another ? We believe that there is not nor will ever be a perfect solution for this problem ; call us "ORM limitations acceptors". Our belief is that an ORM solution has to be designed to fulfill a specific set of issues under a specific set of constraints (which definitely sounds like a product line to me). After all, if you want to go around and around in circles without going anywhere, you are more likely to win the race with a formula one car. If you want to drive home, you will be better off with a standard commercial car : it has been designed for average roads, and priced accordingly. This library will help us handle business specific data in a (DAL).
  2. Data Activities is a library that helps define the business data lifecycle into a specific layer. The latter is a which lies at the core of our architecture : it manages business data, drives the HMI, and as such acts as a perfect (?) .
  3. Data Rules is a validation framework. It will be used to implement our business rules in our activities transitions and our data.

These libraries represent the fundamental abstractions around which our applications will be built. Best practices can be documented around their use, that can be written with our own (DSLs). We have defined two DSLs so far :

  1. SDML (Salamanca Domain Modeling Language) : this language will help us structure our data in order to implement our DAL. But more than that, it will help us create reusable states for our activities, or reusable component for data edition (user controls in Windows Forms for instance). Its representation is very close to a UML Class Diagram.
  2. SAML (Salamanca Activities Modeling Language) : this will help us define our activities. Activities reuse concepts defined in a SDML model. Its representation is inspired by the (BPMN).

Implementing an application with our libraries once the models are properly defined should be quite straightforward. So straightforward that we will be gain a lot by creating two tools that will greatly enhance our productivity :

  1. A Modeler : a graphical tool that will help us visually model our application and serialize it in SDML and SAML formats.
  2. A Generator : a tool that will implement our best practices and take our model as input, in SDML and SAML formats, and generate our C# code. Though it is imaginable to generate a whole functioning application, the developer will have the possibility to customize the generated code to make it more usable, or implement parts that could not be modeled. Usually, developers will ... develop a usable HMI on top of the generated code.

As for now, the libraries are quite in an advanced state, the DSLs definition is in progress and the tools are part of our to do list.