First, for the sake of this post, let's change the syntax a bit. Instead of saying
Entity X
We're going to say
class X < Entity
Basic types, like FirstName, will become less basic and will inherit from Element (or in this case, a derivitive: StringElement)
class FirstName < StringElement
Groups will go from the generic Group(PersonAtAddress) to
class PersonAtAddress < Group
Now, this is not going to be the final syntax, but I want you to think of it as inheritance, because we're going to overload functions.
For instance, an Element should know how to validate itself. A simple example might be
class FirstName < Element
def validate:
return representation =~ /[a-zA-Z-]+/
A more complicated example might make a SOAP call to the validation server defined for that type (we'll see how to do that in a later post).
Elements also need to know how to normalize themselves. For instance, a name might wish to be represented in all upper case:
class FirstName < StringElement
def normalize:
representation.upper!
Another operation you're probably screaming for by now is creation. In this case, the StringElement does the right thing for you, it sets the internal representation to a passed in value. However, you might want to do more. For instance, you might want to keep a map of how often you see each first name to handle statistical based maching. Therefore, you want
class FirstName < StringElement
static Hash seen # would be @@seen in ruby
def initialize(String value):
super(value)
seen ||= new Hash(0)
seen[value]++
Other methods might include update and clear.
For Entities, we need the following operations:
initialize - pass in a value for each of the "member variables" and assign them if they are consistent. Otherwise, throw an exception.
validate - validate the state of an entity
update - update one of the fields of an entity
Update is the most interesting because we could update one or many of the fields. I think it might be interesting to use nil for fields we don't want to update, but I'd rather not. Perhaps a hash? But I don't want to miss a field on accident, and I'd like it to be "compile-time" checked. So, we're back to nils.
class Name < Entity
FirstName first_name
MiddleName middle_name
LastName last_name
NameSuffix name_suffix
def update(FirstName fn,
MiddleName mn,
LastName ln,
NameSuffix ns):
begin_transaction:
first_name.update(fn) unless fn.nil?
middle_name.update(mn) unless mn.nil?
last_name.update(ln) unless ln.nil?
name_suffix.update(ns) unless ns.nil?
rescue => { rollback } #undo all operations in the transaction (maybe a validate fails?)
I'm fairly convinced that a transaction is the right way to handle this situation, but I'm not convinced of the syntax, there are definitely other ways to handle it. For instance, you could have an updater that makes the determination based on the exit strategy of the function.
FirstName::Updater updater(first_name, fn)
It could also handle the nil? case. If the function exits normally, the updater commits - otherwise it rollsback. Regardless, a transaction is needed for exception safety.
You may also delete an entity. I imagine that this should return a boolean on whether or not the delete should succeed, but delete's probably shouldn't fail.
A Group has some additional operations. Since a group is just a collection of entities, it may have an entity added to it or removed from it. It addition, it may also have a list merged into it or part of another list sliced into it.
Let's look at each operation in the abstract:
Add:
original list -> [A,B,C] value -> D new list -> [A,B,C,D]
Remove:
original list -> [A,B,C] value -> B new list -> [A,C]
Merge:
original list -> [A,B,C] value -> [D,E,F] new list -> [A,B,C,D,E,F] new value -> []
The merge adds all of the elements of the value into the list and deletes them from the value.
Splice:
original list -> [A,B,C] value -> [D,E,F], 1, 1 new list -> [A,B,C,E] new value -> [D,F]
The splice takes an array and a begin and end offset and adds those elements to the new list while removing them from the old.
The merge we will call a consolidation and the splice we will refer to as a split.
An important property of a Group involves it's
MetaGroup. The MetaGroup is the Set that consists of all the instances of the Group.
So, let's say we have a Consumer Group. We want to say that a consumer can not appear in more than one group. That means that the order of the union of all groups is equivalent to the sum of the order of all groups. To do this, we say that for every group, it's MetaGroup must be a true Set, and cannot include duplicate entities.
Now, back to the operations. Each one of these operations must take place in a transactional environment. For example, an update can fail because of an implied field mismatch.
What's an implied field? A field in a group may be declared strongly or weakly implied. I'm not sure how this declaration will take place (I hate the .NET attribute syntax). However, once it is so declared, it will enforce conformance for all the elements in a group. A weakly implied field will ensure a no conflict match for a field across all the entities in a group. So, if we say that NameSuffix is weakly implied for the Consumer Group, that means all Consumers in the same Consumer Group must have the same Name Suffix (or a blank one). A strongly implied field removes the ability for blanks to match.
Ok, this post has gone on long enough. In future posts, we'll return to the formal specification of these operations.