DDD: Using the language
Design Patterns, Elements of Reusable Object-Oriented Software:
"The key to maximizing reuse lies in anticipating new requirements and changes to existing requirements, and in designing your systems so that they can evolve accordingly."
This is an interesting chapter. While it is more of a case study and a summary of all the patterns and techniques that have been mentioned so far, it also serves to illustrate the important concept of reuse. The scenarios presented on p. 173 give examples of changes that need to be handled. I can see how a layered architecture will help maximize the reuse of the system domain and how well it can accommodate changes. Without the discipline to enforce a layered architecture from day one, the model will quickly become littered with all sorts of application logic that will hinder changes to the model.
However, I still find it very hard to come up with a model that is this robust. Evans already mentions that he did not show the evolution of the model in this chapter since he wanted to keep the chapter short and focused. Yet, it still seems hard to me how he came up with it in the first place. No doubt talking to domain experts must have really helped. They are the people who have the experience and can tell you how the customers will be using the software. And they are the people who can help you greatly in encapsulating changes. I like how the chapter also hints at the complexity of software that have to be used in different countries: the tax ID cannot be used as a unique key to the customer in an international company (p. 167). With changes that might even require handling international cargo, the model really becomes important. A simplistic model might work well for the first few iterations but will fail when new unprecedented requirements come in.
From reading this chapter, I got a good grasp of what it means to accommodate the different changes. It is not sufficient to only prepare for the initial version 1.0 release of the software. Instead a good software developer must be able to take the software in new directions from there without rewriting everything. And the techniques that Evans has presented serve as good guidelines for that purpose. Of course, refactoring is also going to be very important and that is the focus of the next section.
There is this rather controversial book entitled Prefactoring that seems to go against the principle of YAGNI: You Ain't Gonna Need It. I have not had the pleasure of reading it but it also seems to offer some guidelines on how to initially design the system based on past experiences. A short introduction to prefactoring can be found here.DDD: Life Cycle of a Domain Object
It's funny how sometimes when you read about something and that something pops up somewhere else. So, I was reading about the ActiveRecord (it's like the Active Record pattern in P or EAA but has more features so it might be a misnomer ) module in the Rails framework and they mentioned about using aggregation in the models. Here's the comment blurb directly from the source code:
rails-trunk/activerecord/lib/active_record/aggregations.rb:
Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is] composed of [an] address". Each call to the macro adds a description of how the value objects [emphasis added] are created from the attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object) and how it can be turned back into attributes (when the entity is saved to the database).
More information can be found on Rails aggregation here. Validation support is also built-in to the models.
I am sure that other frameworks out there support aggregations out-of-the-box as well but it is interesting to know that Rails does support some of the best practices in domain modeling. And how I would have used aggregations in Rails without even bothering with the fact that there was a pattern called Aggregations. Frameworks that make such patterns almost transparent are a great way to pick up patterns even without realizing it.
Aggregates are great for simplifying the issue of maintaining and handling transactions. Even if your database supported pessimistic or optimistic offline locking you would have to be very careful to order your database accesses to avoid deadlocks and. By using an aggregate properly, you can get away with some of that responsibility since all access to the database can only be done in larger chunks (the aggregate).
After discussing Aggregates, the chapter goes on to Factories. Factories is a generic term applied to the creational patterns such as the ones mentioned in Design Patterns: factory method, abstract factory and builder. Evans does not discuss each one in turn but shows how the concept of Factories are important to encapsulate the process of object creation so that that process of creation does not complicated the underlying domain. Some objects are hard to construct so expressing its construction process as a complicated region of code might distract the reader from the real domain underneath the system.
Finally the book presents Repositories. Repositories seem like a wrapper to me around the complex process of object retrieval. Just like object creation, object retrieval itself can be a complex process. For instance, if you are using a database, it is not uncommon to have to do joins across tables to actually get at the data that you want. However, executing a raw SQL query is not very clean; it exposes the encapsulation of what your tables are. Evans made sure to include the fact that sometimes, the framework already does a decent job of information retrieval for you. At least, that is the case with the Rails framework using reflection and meta-programming.
1 class Post < ActiveRecord::Base 2 has_one :author 3 end 4 5 class Author < ActiveRecord::Base 6 belongs_to :post 7 end
1 CREATE TABLE posts ( 2 id int(11) NOT NULL auto_increment, 3 title varchar default NULL, 4 PRIMARY KEY (id) 5 ) 6 7 CREATE TABLE authors ( 8 id int(11) NOT NULL auto_increment, 9 post_id int(11) default NULL, 10 name varchar default NULL, 11 PRIMARY KEY (id) 12 )Listing 2: The SQL table that represents the relation in Listing 1
With that you can do is:
1 # find_by_title is a method generated automatically based on your table schema
2 post = Post.find_by_title("First")
3 post.author # this will return the right author for that post
It works for many-to-many relationships as well. More information can be found here.
What's interesting is that the three patterns discussed here all help to make sure that the underlying model of the system remains as uncluttered as possible. The implementation and the model should be bound together as advocated in Chapter 3, but it's also important that details of implementation - that might be the inherent limitation of the framework or programming language - do not interfere with the model. If there is too much interference, then no matter how you try to use a Layered Architecture, it will still be hard to extract the underlying logic for the model from the code.
DDD: A Model Expressed in Software
This chapter begins to define the importance of associations among different models and how to refine those associations. Refinement makes models that are simpler to understand. Furthermore, careful refinement also reveals overly complex associations that might not actually exist in the domain that we are modeling. It is best to prune those redundant associations early to have a model that is both simple and accurate.
To facilitate describing associations between objects (not models), Evans presents four patterns: Entities, Value Objects, Services and Modules.
Entity is a pattern used when you need to distinguished an object by its identity, rather than its attributes. For instance, in a grading application, you want to distinguish students based on their name. You cannot use any of the other attributes such as their total points, their discussion section, their major. These variables may change but the name of the student is unlikely to change. That is the identity of that student. Maintaining this identity is important because that is the only way to determine if you are really who you claim you are.
If you are familiar with databases, you realize that in each table, there is a primary key that will uniquely identify the row on that table. Even if that row is deleted, the same primary key will should not be used again for a new row since it might be confusing to the application. That is why most databases provide the ability to auto-increment the primary key to guarantee that it is unique for all rows and that no row will have to use a previously issued primary key. No chance of a mistaken identity with a deleted row.
In a blogging software, each post should be identified by GUID (global unique identifier). This allows you to track changes to that post. For instance, the author might decide to modify the contents of that blog post, but it still the same blog post. Good RSS readers make use of the GUID to determine if a post is new and will mark it as such. Those same RSS readers can also determine if the post is just a modified post and will show the difference between the previous version (from local cache) and the new version (from the server).
In short, the Entity pattern lets you identify an object uniquely even if the attributes of that object might evolve. As the examples above show, there is a whole lot of use for entities. However, there is also the temptation for over-using the Entity pattern. Sometimes some objects just do not care about their identities. For that you should use the Value Object pattern.
The Value Object pattern is useful when you care about the attributes of the object instead of its identity. For instance, when using numbers to add, you do not care if you do not distinguish between '7' and another '7'. All you care about is the value. A '7' is a '7' is a '7'. You would care if suddenly a '4' became a '7'. That is why, value objects are immutable. Once you create a number '4' you expect it to remain that way. Immutable objects are better for performance since you never have to keep track of their identity. In fact, the chapter talks about ways to make value objects faster. One method involves using the Flyweight pattern.
Not everything can fit into a Entity or a Value Object. It would be very constraining to have everything be either one of those objects. Instead, the book mentions a third pattern: Services. Use the Service pattern when your object does not really fit with the definition of an Entity of a Value Object. There is the temptation to make everything a "service" and eschew good object-oriented design patterns. Use the Service pattern only when it becomes awkward to actually represent the object as an Entity or Value object. Even then, try to employ other design patterns such as the Facade or Singleton to keep the design manageable. A Mediator might be necessary for more complex systems.
The final pattern that was mentioned is Modules. Modules should be used to separate code into groups that are decoupled from other groups but have cohesiveness within the same group. You can have groups nested in groups. Most developers already use modules. For instance, in Java, the concept of packages is common because it is a convention. However, the focus here is on naming the modules correctly so that they reflect the ubiquitous language discussed in Chapter 2. Refactoring modules by renaming or moving files is not practiced much because it can cause a cascading effect to the source control. As such, careful naming should be done early.
This chapter has discussed the next step in model-drive design. And that is how to represent the model in software. While the book's primary focus is on using object-oriented languages, the end of this chapter also presents some methods for combining different languages together. The patterns help designers express the model in software.
DDD: Isolating the Domain
To have a successful domain model, we need to separate the different concerns from the model. A domain model is easily cluttered with code from the UI, database and other supporting code. When this clutter happens, it becomes harder to sieve through the code and determine what's the actual domain. The current trend to mitigate this problem is by separating the system into different layers and minimize the interactions between those layers.
This chapter presents a general layered architecture that comprises:
- User interface layer
- Application Layer
- Domain Layer
- Infrastructure layer
Two patterns from the Patterns of Enterprise Application Architecture (P of EAA) were briefly mentioned:
I found it useful to skim through those patterns from the P of EAA book while reading this chapter.This chapter also gives some warning about architectural frameworks that might seem to be useful but actually stymie the development of a domain model because the framework demands that files fit certain constraints. This is especially true if you are following the framework blindly. Steve McConnell talks about this in Code Complete 2 when he mentions programming into a language instead of programming in a language. McConell was using Visual Basic to program the UI and Visual Basic makes it really tempting to place all the business code into the forms (.frm files). In fact, Visual Basic advocated keeping everything in the form file and did not make it easy to delegate functions from the .frm file to the associated .bas file. But that did not stop McConnell. He came up with his own convention to minimize the business logic in the .frm files. The moral of the story: do not just program in the framework, program into it.
The "Smart UI" anti-pattern is a very common pattern for novice UI designers. When using a visual editor that allows drag and drop of widgets and auto-generation of code, most novices just dump all the code into the auto-generated placeholders. For instance, if you are using Visual Studio's Form editor, it is easy to just double click on the button and be transported to the callback code that is to be executed when that button is clicked. Without analyzing further, a user might be tempted to put all the code inside that callback handler instead of delegating the function to some controller.
Often, auto-generated code leads to code that is hard to maintain and hard to understand. Auto-generated code does not try to be "smart"; it tries to be safe. When creating new functions, the safest thing would be to call them function1, function2 and so on. This helps eliminate the chances of a name clash. But these names make no sense to a developer.
What the "Smart UI" anti-pattern is trying to show, is that you can still have a cluttered code base if you are not careful with the tools you use. A visual editor is suppose to make it easy to generate the user interface, but it is up to the developer to be skeptical about how to organize the code. Do not just blindly follow the suggested auto-generated placeholders.
In conclusion, this chapter focuses on the need for separation of concerns between the different layers. Leaving the model clean from all extraneous code helps developers focus their effort on creating a richer model instead of wasting time trying to figure out what actually belongs in the model and what does not.
DDD: Binding Model and Implementation
This chapter emphasizes the importance of the model and the implementation. It is useless to use a different model when designing and a different one when actually implementing the system. Not only does this mean that whenever something change you are going to have to modify two models (a prime candidate for introducing inconsistencies and bugs) but you would also have deal with the mapping between the two models. It is always easier to deal with just one model. And from the previous chapter, that one model will help promote the use of an ubiquitous language.
The process of binding the model and its implementation is called model-driven design. And together with the ubiquitous language, forms the basis for domain-driven design. Model-driven design is more easily implemented in a language that supports modeling. Object-oriented languages do this well and thus is more suited for this task. Procedural languages hide the model by operating in terms of passing data around between functions.
A good article always gives concrete examples. And this book has done exactly that in the example for the PCB board. I have had some experience using CAD tools (HDL Designer) for designing microprocessors in one of my computer engineering class so the terms he used are similar to the ones that we use when designing. Instead of nets we called them signals and in addition to buses, we also had bundles which could contain signals and buses. The CAD tool was helpful in letting us connect nets and buses using the mouse, but that was just a visual representation of it. Underneath all that GUI, the design was still embodied in VHDL (or Verilog) which is stored as a text file. And sometimes, when using buses, you have to manually edit the names of the individual nets in a tabular manner.
The mechanistic manner would surely work since everything was basically in VHDL which is very editable by text manipulating scripts. But the model-driven design manner was much cleaner to a programmer. The ability to unit test each the methods were also an added advantage.
One thing that was probably enforced but not mentioned explicitly in the text was that each net and bus name were unique. You cannot have buses with the same name in the same layout because that is not legal syntax and would only confuse the simulator. Nets and buses with the same names are going to be treated the same.
The example of Internet Explorer's favorites is a good example of the importance of a model that matches the user's mindset. A matching model has a lower learning curve for the user and lets the user make use of the features of the application easily.
In conclusion, this chapter shows the importance of having a model that is tied closely to the implementation. Without this tight relationship, the implementations and the underlying abstractions become harder to reconcile as the project requirements evolve leaving the system in a state of code rot.
DDD: Communication and the Use of Language
If you have ever played the game where a group of people sit in a row and you try to to pass a message verbally from the first person to the last person, you know how fun this activity can be. Not only is the sentence deformed in every way imaginable, but the original meaning is usually completely lost.
This chapter emphasizes the importance of communication. All major software projects involved more than one developer, so it is important that every developer communicates clearly to one another. One way to do this is to use well-established terminologies. A queue to a data programmer might mean a data structure but to a non-programmer, it can validly refer to "a braid of hair worn at the back". So it best to make sure that everyone is on the same wavelength before using a term. As the book claims, a project faces serious problems when its language is fragmented across the team members.
And thus the concept of a ubiquitous language arises. With this ubiquitous language, the same terms can be applied in verbal and written communication. You use the same terms in code and in your documentation and in your daily communications. It's unlikely to have this ubiquitous language from day one of the project. But as your project matures, you will find yourself building the vocabulary of the language and refining it to make it more precise.
One very interesting theory in anthropology is the Sapir Whorf theory. It says that you can only understand the world if you can describe it using some language (not necessarily written since some languages have no written form). So the canonical example is that the Hopi people are not able to come to grasp with the notion of future and past since their language does not support it. They have trouble understanding what "yesterday" or "tomorrow" means. The same thing can be applied for domain modeling. Without a suitably rich language and vocabulary, developers will never be able to see what they are actually missing. Enhancing your language helps enhance your model as well.
There were some terms in the diagrams in the chapter that I did not really understand. For instance, cargold and hazmat. I found out that hazmat referred to HAZardous MATerials but I still have no idea what cargold is. We will probably revisit this diagram soon but it would be good to know what cargold is.
Evans dedicates a section for modeling out loud. After reading that section, it felt like a CRC: Classes, Responsibilities, Collaboration session to me. In a CRC session, you have people talk about the problem and try to create suitable classes as models for the problems. This seems like a good way to come up with some models as well. CRC sessions are highly interactive and everyone gets a chance to say something. We can use the general idea of CRC cards without getting too much into the details.
UML is good for getting ideas across quickly in a diagrammatic manner. However, Evans is quick to realize that a diagram is just a diagram and nothing more. You can definitely represent a lot of things in a diagram but there are certain things that are easier to describe using words. In effective communication, the key is to convey the intent as clearly and quickly as possible. When you have to scrutinize the picture for "hidden" meanings, then you might as well use text. Over-advocating one tool is always a bad idea. All tools have their limitations and knowing those limitations enables you to make judicious use of them.
All in all, by using an ubiquitous language in as many parts of the development as possible, we can come up with a consistent way of describing the system that makes sense to the stakeholders. As the book says: "[O]ne model should underlie implementatoi, design and team communication. Having separate models for these separate purposes poses a hazard."
DDD: Crunching knowledge
While reading the introduction to this book, I was curious as to how much the model referred to in this book is similar to the metaphor in XP. I was not able to find anything in the index about XP or metaphor so I assume that this book does not dwell on that issue. However, this issue of a model and metaphor is pretty interesting (to me at least).
In Extreme Programming Explained, Kent Beck defines the system metaphor as:
A story that everyone - customers, programmers, and managers - can tell about how the system works.
A model for the domain seems to be fulfilling some of that task. With a suitable domain model, one can more easily implement and design the system to correspond to the model. The model itself also serves as the basis for communicating amongst the developers when they refer to parts of the system. Instead of calling the method funcA, they will call it applyFilter for instance. Moreover, the model is an abstraction of the most important features of the system; it allows one to describe the internal workings of the system in terms of these abstractions. With these abstractions, the developers can communicate better with the people who are using the software. You do not talk to Photoshop users about the convolutions involved mathematically to change the image; you talk to them in terms of filters and masks.
Unfortunately, since less than 2 pages were allocated to discuss the system metaphor in XP, most people do not actually know the real meaning of the metaphor and it is hard to compare it with the concept of a domain model.Anyway, Chapter 1: Crunching knowledge presents a process of effective modeling. It's called knowledge crunching because like number crunching, you start of with globules of unrelated data and try to synthesize something. And the best knowledge crunchers do not work alone; they talk to domain experts. Want to create software that deals with gene structures? Talk to a cellular biologist. The best knowledge crunchers also see "real" pervasive links (not fragile links) between the clumps of knowledge. They can formulate new relationships and remove obsolete ones. Their knowledge of the system grows as they become more experienced.
Evans has outlined the methods for effective modeling:
- Binding the model and the implementation
While abstractions are nice for communicating and getting the initial idea across, building concrete instances are also important. Building something forces you to think about the parts that are still missing from your domain model. - Cultivating a language based on the model
- Developing knowledge-rich model
- Distilling the model
Do not be afraid to throw something out or formulate a new model if the old models do not scale well. Your previous models serve as guides and pointers for you. Your new one model will build upon those lessons that you have learned. - Brainstorming and experimenting
New paradigms come and go. You need to know when to embrace them.
In the example about shipping books, Evans shows how important it is to capture important domain knowledge and not let it be hidden in the code. In his example, the concept of overbooking was being implemented using a simple if... then clause in the code. While this certainly works, it does not immediately convey the message of overbooking to the code reader. By abstracting that overbooking if... then clause into a different class (a strategy), it makes it clearer what the if... then clause actually serves. Clear code is not only easy to read and understand, but it also conveys important underlying features across clearly.
Inheritance and relational databases
I am reading Martin Fowler's Patterns of Enterprise Application Architecture. I have been flipping between patterns because the project that I am documenting, Typo, makes used of most of them. Actually Typo is built on top of the Rails framework, so it is more correct to say that Rails makes use of them and Typo builds on them.
To get a better feel for Rails, I have been perusing Agile Web Development with Rails. It's a nice book. More tutorial-like in contents than a real book on the architecture of Rails. Between reading that book and the source code, I think I have a pretty good idea how Rails is structured. The source code is not as easy to read as I had hoped. Some files are over 2000 lines long because they were comments that were lengthy examples of how some concept works. Also, there is heavy use of meta-programming around so it is easy to miss something since you cannot just do a search for that term that you expect.
Anyway, there are three patterns in Fowler's book that deal with how to handle inheritance within databases. I read them all and was really astonish that I have a sense of deja vu for these patterns. Seems like we had already covered these patterns in my database course albeit with different names for them.
So here is how those patterns correspond to the picture above:
- Single Table Inheritance - The Null Value Approach
Create a super-node kind of table that can accommodate all children of the current class. Fields that are not used will be left null. - Concrete Table Inheritance - The OO approach
Create a table for each concrete class. Fields common to classes will be duplicated across the tables. - Class Table Inheritance - The E/R approach
Class Table Inheritance will try to eliminate duplication between elements by specifying a parent class and then using foreign keys to link them together
The Class Table Inheritance scheme is potentially going to be able to save space but it is hard to actually retrieve stuff since you have to do multiple joins on the tables.
The OO approach is going to be really explosive! For every possible combination of child-parent relationship, you are going to have a table for it. Each kind of relationship will thus only fit in table by default. I can imagine that if you ever need to change you schema, you will probably be in for a lot of trouble.
And now, as if that sense of deja vu was not enough, Rails also uses one of the methods above fpr handling complex relationship between tables. Rails has built-in functions for associations and inheritance. It uses the simplest of the three methods above: Single Table Inheritance.
SAIP: Software Architecture in the Future
This is the last chapter of the book. Somehow, I feel that most of the contents here should have been placed either in the prologue or in the preceding chapters. For example, the evolution of software from subroutines, modules, objects and frameworks should have been mentioned earlier to give the reader an idea of how software has evolved.
This chapter mentions the shortcomings of the some of the techniques introduced in the previous chapters and I am glad that the authors acknowledge those shortcomings. For instance, in Chapter 5: Achieving qualities, readers were presented with a very unclear description on how to proceed once the development team has a plethora of tactics that they want to employ. Chapter 5 suggested that from those tactics, one could easily pick a right type of architectural style to apply. I am doubtful that it is that simple. If it were that simple, then there would not be a need for all this discussion in the first place.
The authors also discuss the existence of documentation within a tool environment. Again, I feel that this really should have been mentioned in the chapter about documenting the architecture. I agree that the authors might be wary about mentioning tools that they have neither used nor analyzed closely. But a brief mention of those tools might actually be helpful. After all, those tools are used in practice so they must be beneficial in some way. And more readers will be familiar with it compared to the Dali tool that was discussed extensively.
I appreciate the book devoting a sidebar to software architecture in education. However, regardless of how many course you take in the university, software architecture cannot really be taught. You can be exposed to a series of techniques like what was done in the book but then again, you only really learn those techniques if you apply them in real life scenarios. And that's why I am a fan of classes that have lots of projects. There are many things that have been left out from this book (and judiciously so) for the sake of keeping it simple. As the title of the book hints, software architecture is something that exists right now as a form of practice, and there has not been any significant advances in trying to codify the theory of it.
This book is an attempt to describe software architecture. It's not easy task, and while the book could be improved, it does a pretty good job at it.
Why Java Applets did not make it
There was some discussion in class today about why Java Applets did not take off as much as Sun Microsystems had hoped. While I really do not know the answers, here are my thoughts.
- No broadband. 10 years ago broadband Internet was not as pervasive as it is today. So there wasn't really much need for highly interactive web pages. Imagine trying to download a few megabytes worth of data over a 28.8 kbps modem. Now imagine if every web page out there embedded an animating Java Applet.
- Heavy JVM. Even today, most people don't really like web pages embedded with Java applets. Windows does not really ship with the latest version of the Java Virtual Machine. Our university uses a classroom system called Compass based on Java. Almost nobody likes it. Enough said.
- Browser compatibility. Not every browser in the 1990s supported Java Applets. And most people did not bother installing a new one.
- Java was not a buzzword (yet!). Most consumers did not know or care about Java. And pointy-haired bosses did not know enough to jump on the bandwagon.
- No web designer knows the language. This is very important. The first few Java Applets were really hideous compared to what was out there. I suspect that it was because web designers did not get on board yet. To most web designers. Java is a heavy language to learn just to make animated applets. You need to know about threads, classes, methods and objects. Even today, most web designers prefer the simplicity of HTML, CSS and some javascript.
- It just looks weird. The UI from the AWT looks weird on the browser. And that makes the entire web site feel clunky.
I am interested in hearing what other people think. Java Applets were something introduced in the wrong time period. Had it been introduced later when it was more mature, it might have appealed to more people. I guess it is a compromise between how early to enter the market and when to wait for maturity of your technology.
