First let us look at what software architecture is. As a discipline I can't help but think of it as a declaration of war on a problem. The problem domain can be almost anything you can think of, and can range in difficulty from trivial to almost infinitely complex. If the problem is trivial then we are very likely going to crush our enemy without much effort. On the other hand if the problem is complex, we need to be more careful, we need to consider both tactics and more importantly strategy. We need to choose the appropriate weapons to aid us, and we need to use all the tricks we know, and then some.
So, to recap, we need to choose the right weapon, something versatile, deadly, powerful. Thats easy, let us choose Scala, and actors. Our tactics and perhaps to a lesser extent our strategy depend to some extent on our choice of weapon. Likewise our weapon choice may depend on our tactics and strategy. As it happens, at work we are comfortable that Scala gives us a better range of tactics and strategy than any other language for most use cases.
Now we are on our way to giving the problem a good hammering, but we don't want to be overconfident. Now we need to ensure our strategy is good, our tactics are sound, and that we have a few tricks up our sleeve.
The legendary strategist Sun Tzu said 'all warfare is based on deception'. We are going to deceive in two very important ways. First we are going to deceive in terms of hiding the composition of our forces (this is represented by our model of the problem domain). Second we are going to deceive in terms of when we are going to attack the problem (we are going to defer, using actors), more on this in the next post.
A Deceptively Simple Model of the Problem Domain - Conceal the Composition of your Forces
We all hate a polluted and overly complicated model of the problem domain. What is it that causes this complexity ?
- lack of thought
- lack of understanding of the problem domain
- persistence
The first two I will not comment on, but persistence is worth looking at. There are so many problems caused by the persistence facet in any project I could almost write a book about it :-) Ours at work boil down to the need to persist in a relatively flexible way, sometimes relational, sometimes post relational.
By the time we have prepared our domain for persistence in just a relational store we have either written an essay in XML, or given our code a lethal injection of annotations. Simply put, we lose the will to live. It is really quite upsetting. Worse than upsetting, it is actually in my opinion dangerous to the health of the project. The elegance, intention and pretty much all other meaningful parts of the model or abstraction of the problem domain are literally lost in a maze of tangled concerns. Thats bad mmkay ?
Now we are going to take our first steps towards a better model of the problem domain. First we are going to use Scala's case classes, and only Scala's case classes to model the problem domain. No tangled persistence gibberish. Yes I consider it to be gibberish when it has invaded my sacrosanct model, where it clearly does not belong.
It is time for some pseudo code. Please note this is overly simplified and not particularly robust, it is intended to be concise :-)
sealed trait SimpleEntity {
var id = 0L
}
case class CannonFodderGruntType1(description: String) extends SimpleEntity
case class CannonFodderGruntType2(description: String, weapons: Int) extends SimpleEntity
Urm, that is it. Hard to find a mechanism in any language which offers so much in so little code. I won't list the goodies we get for free here, they are well documented.
At this point, it looks like we have a pushover army, unable to persist itself. That is true, for now. We are deceiving our enemy, concealing the true composition of our forces until the last possible minute. Enter Scala's implicit conversions.
Once we have decided how we want to persist our domain we use a handful of lines of code to tell it how it should persist itself. If I use as an example Squeryl, an excellent SQL DSL library.
class SquerylPersistenceWrapper[T <: SimpleEntity](grunt: T) extends SpecialKey
implicit def wrapSimpleEntity[T <: SimpleEntity](grunt: T) = new SquerylPersistenceWrapper(grunt)
At work we have wrappers for JDBC based store integration and perhaps in the future other alternatives.
Suddenly the enemy (durability) sees that we are more than a match for it. At the last minute we persist our domain, as we wish, and there is nothing our enemy can do about it. Better still, our domain model is concise, unpolluted and therefore has a better chance of remaining meaningful long into the future.
To wrap up, here is very typical sample of what a domain layer looks like after a lethal injection of annotations.
@Entity
@Table(name="CANNON_FODDER_GRUNT_1")
case class CannonFodderGruntType1(
@Id @GeneratedValue(strategy=GenerationType.AUTO, generator="CFGT1_SEQ")
@SequenceGenerator(name="CFGT1_SEQ", sequenceName="CFGT1_SEQ")
@Column(name="CFGT1_ID", nullable=false)
@BeanProperty var id: Long) extends SimpleEntity {
@Column(name="DESCRIPTION", nullable=false, length=254)
@BeanProperty var description: String = _
def this() = this(null)
}
Urm, yes, well, something along those lines :-) The intent is most definitely in my opinion lost.