DDD: Distillation
Upon seeing the infamous Maxwell's Equations left on the blackboard from a previous class, my complexity theory professor immediately ordered us to erase them with "extreme prejudice" [sic]. A short anecdote to show that not everyone cares for electromagnetism, even if they have a degree in ECE.
The book's title is Domain-Driven Design: Tackling Complexity in the Heart of Software. And this is the chapter that truly details how to do it. The previous chapters in the book were advocating best practices for model-drive design and how to cope with shallow models, multiple models, and the usual office politics. But it is in this chapter, I believe, that we deal with the real complexity in software: complexity in the model itself. And Evans has referred to this process as distillation. In this chapter, he attempts to define how to distill the core domain. Unfortunately, I don't really buy everything he says mostly because I do not have enough experience with this and because his description is not all that clear.
As you refactor to deeper insight, there is little that you can do to prevent the model from growing. As you understand more of the domain, your model will begin to reflect that. Soon, the model grows to the size that not everyone can understand it. New developers brought on to the team will have a hard time adjusting. By distilling the model, you end up with the core domain: the set of features that are essential to the system. How does one define what essential mean? Evans thinks it is the real business asset of the entire system.
Therefore, I can see the rational behind having a domain vision statement. Evans proposes that it be a page long. But a good domain vision statement should be summarized in as few sentences as possible. And this vision statement should be displayed prominently where everyone on the team can see it. It points the way to the core domain. And your team can supplement this with a highlighted core.
I really agree with Evans on how often engineers get distracted by a complex problem that might not actually be part of the core domain of the application. Not only do engineers like dealing with a more "well-defined" problems such as time zone conversion but they also find it very hard to deal with problems that have arbitrary rules such as accounting or business software. These problems are not elegant since there is no elegant solution like Maxwell's solution to them. This is really where analysis patterns come in. Even if there is no elegant solution, at least there are well-tested solutions that can tackle some of the arbitrariness.
Because engineers do not like to get into the hairy details of business and domain logic, the core domain usually gets pushed to the less skilled developers who might not have an inkling of how things are supposed to work in the first place. Because of this, no matter how good the generic subdomains are, the core domain still suffers.
Again, I found that this chapter had way too many "patterns" or terms. In fact, because I found the terms hard to distinguish, they actually made reading the chapter harder. I was constantly thinking of where else to apply these terms but none came to mind. I will probably change my mind when I reread this chapter again in the future when I have dealt more with some of the issues that Evans mentions. I will probably write my newfound insights after the class on this chapter.
DDD: Maintaining Model Integrity
What Evans has been advocating about model-driven-development so far has mainly focused on maintaining one consistent model throughout the development team. However, what happens when you have multiple teams working on different parts of the system? For instance, it is not uncommon to outsource part of the development to another group. What happens to the underlying model then? Should all teams still have the same model? Evans argues that they should not. Instead they have different models but aspects of the model that are important to everyone should stay unified and the parts that are less important need not be. Evans presents some patterns that might help. [Patterns are described in the middle portion of this post; interesting aspects of the chapter are at the end of this post]
Bounded context. Every model should be accompanied by the relevant context to which it applies. The context refers to team organization, usage within specific parts of the application, code and the database schema. The model should be kept consistent within this context at all times regardless of external influences. The question is: how much "context" is needed to actually bound a model? Also, what does one do when there are overlapping contexts?
Even within a bounded context, there might be undetected overlaps. For instance, when Team A talks about "Charge" and Team B talks about "Charge" are the two "Charges" the same? What aspects do they differ in and what aspects are they similar in? Recognizing these differences are very important to ensure that your contexts are really bounded properly.
Continuous Integration. Continuous integration helps maintain the cohesion within each context and reduces internal fragmentation. How would one do continuous integration? One has to ensure that a strict schedule for integration builds and automated testing is being carried out. Furthermore, within the same context, developers should strive to use the Ubiquitous Language. Continuous integration solves the problem of fragmentation within the same bounded context, but what should one do for multiple bounded contexts?
Context map. When multiple bounded contexts exists, it is important to find the point of contact of each context with one another. Each bounded context should be identified by a communicative name and that name should be made part of the Ubiquitous Language. Once the name has been decided, then one can create a map that shows the relationship between each bounded context. The names give identity to each context so that everyone on the team can refer to them clearly. The map gives a picture of how everything relates together.
Shared Kernel. Sometimes, it is hard to identify a boundary between different context. Even if this boundary exists, it might seem artificial. In that case, one should treat the intersection of the different contexts as a shared kernel. A shared kernel should undergo some form of continuous integration as well to maintain its coherence between the different contexts. But because this shared kernel, as its name implies, is shared between contexts, integration will not be done as frequently since it has to be agreed upon by members of both teams. A shared kernel is a good choice in place of a context map especially if two or more models are closely related. Using a context map in that case will only lead to an unnecessary artifact that serves only to confuse.
Customer/ Supplier Development Teams. This pattern deals more with how different teams should work out dependencies between them. For example, Team A might depend on Team B's work to accomplish the task. Team A might accuse Team B of not following the specifications and vice versa. Also, if this is not the case of a shared kernel, then it becomes harder to integrate since the two teams might not use the same model. The Customer/ Supplier Development Teams pattern helps mitigate this internal conflict between teams by establishing a set of guidelines. Using automated tests that check for interface compliance, arguments can be avoided (most of the time).
I am not really sure why this is a pattern; this is common sense. If two teams are going to work together, then they must agree to some specification to make their parts work. It's hard to justify when a pattern is common sense and when it is a profound revelation to some. However, I am pretty sure that this is the case of a common sense pattern. A more useful pattern would be how to actually do the specification without spending too much time and how to ensure that teams to do not go at each other's throat.Conformist. I suspect this pattern happens more often than the previous one. And for good reason too. Having two teams come together to agree on an interface is just asking for trouble (most of the time). Designate one team as the upstream team and have the other team comply to the interface needs of the upstream team.
In a setting where there is one management or one chief architect, the Conformist pattern is easy to apply. Some might think it is rule by dictatorship but this saves a lot of time for everyone. Using the Customer/ Supplier Development Teams is a waste of resources. If one wishes to maintain fairness, then rotate the team members so that they can be in the upstream team or downstream team.
Anti-corruption Layer. This is just the Facade or Adapter pattern. The only difference is the intent. The Anti-corruption Layer provides clients with functionality in terms of their own domain model. This makes communication within the group easier since the interface now complies with the model.
Separate Ways. Recall the Standalone Class pattern from Chapter 12. This is similar. If one can get a bounded context that does not interact with other contexts, then it is usually best to maintain that isolation. Bounded contexts in isolation are free to evolve on their own and thus do not incur the overhead of a context map or shared kernel or the other patterns that deal with interdependence between models.
Open Host Service. Define a protocol that gives access to your subsystem as a set of services. This can be formalized using the Published Language pattern. I am not really sure what this pattern is. Is it just a well-published API for the model, or a more heavy duty system like a Web Service?
Published Language. Use a Published Language to communicate your domain information to people who are not in the development team. It is best to conform (the Conformist pattern?) to a language that other developers in the field are using. Not only does this aid in communication efforts but is also saves developers the trouble of coming up with their language and the tools to translate them into. Published Language like the Ubiquitous Language share some common goals.
This is a very long chapter with many patterns. At the end of the chapter, Evans refers to all of them again and defines the relation between them. Some of these patterns seem very abstract and it is hard to actually determine if you are following the pattern or just relying on intuition. In fact, some even almost seem managerial. There is nothing wrong with managerial patterns, but those are patterns that I am not really interested in.
I must give credit to Evans for the elephant example that he has at the end of the book. It makes so much more sense than the shipping or banking or accounting examples he has been using throughout the other chapters. The elephant example seems to hint that there must be a chief architect for most of the patterns here to work. If every team develops in isolation then it is hard to integrate things properly. There must be someone who has a broader view of the entire development process. And that person is inherently missing from the chapters of the book so far.
Another thing that I like about this chapter is the Transformation sections. I found this to be the most important underlying factor to these patterns. Without the transformations, each pattern will just be static commitment. But for a real development effort those patterns will have to evolve depending on the insights offered by continuous refactoring and integration.
DDD: Relating Design Patterns to the Model
I am actually a bit surprised that this chapter is in the book. It did not even occurred to me that there might be a separation between design patterns and the patterns mentioned in this book. When I read the chapter that says that there is a close binding between the model and the implementation, I was already convinced that certain design patterns could be applied when modeling. After all, all of the design patterns are language agnostic so they are not tied down to implementation details. Also, we have already been looking at patterns from various patterns book, so I convinced that design patterns are high-level enough for domain modeling.
For instance, when the book talked about Aggregates and Repositories, I was under the impression that the Facade pattern might come in useful when we are modeling since Facade provides a higher-level interface that makes the subsystem easier to use. Or if my system will be relying on information from another system, I might use the Adapter or Proxy pattern between those two systems to make the two systems work together.
The chapter elaborates on why the connection pattern and the design pattern might be blurred. Some people have the notion that Design Patterns are technical hacks that one applies to overcome certain limitations in the programming language. Instead, I see them as general ways to think of how to design things. The technical details are important, of course, but the intent of the design pattern is even more important.
So, either I am completely off and the book is stating something very different. Or I am right in my assumption on how to apply design patterns and the book is merely stating the obvious.
DDD: Applying Analysis Patterns
The term Analysis Patterns come from Fowler's book, Analysis Patterns: Reusable Object Models. In this chapter, Evan tries to show how applying those patterns can actually help create a more mature domain model. Utilizing past experiences in the field help reduce the mistakes that beginner designers make when dealing with a field for their first time.
Drawing on the experiences of other software developers is actually very useful. It is not the solution to your problem but at least it shows one way that might work. Furthermore, because Analysis Patterns is described in a way that developers are able to understand it, developers are actually able to come to grasp with the model more quickly. You see terms used in the analysis pattern that can become part of your development teams' ubiquitous language.
My only concern with analysis patterns is that they might be hard to read. They focus on one field in great detail. It is not easy to actually explain an entire accounting system without some basic background on the reader's part. And for readers like me who have no idea how accounting actually works, this chapter was actually rather hard to read. However, it is this detail that makes it valuable to developers working on the project. It's just that most people will not pick up this pattern to read for fun.
Fowler has posted a .pdf of some more accounting patterns on his website for those who are interested.
DDD: Supple Design
Supple Design (p. 244):
"A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way. Look at the design of software that really empowers the people who handle it; you will usually see something simple."
Evans goes on to say that the early version of software will probably be rigid and stiff. And that many do not even acquire the suppleness that he is talking about.
What Evans says is a good point. But it seems to be very hard to achieve. I have seen systems with a lot of overengineering and lots of accompanying abstraction layers piled onto it. It's hard to understand it all at first. But after looking at it for a while, trying things out and actually reading the source code, things are not that bad overall. And when you and the other developers get into this mindset where you understand the code so well, you find it really hard to be able to break out of the box and actually evaluate the complexity of the design. At least until it has become some big ball of mud and then no one knows what to do with it anymore.
In this chapter, Evans offers some patterns that lend suppleness to a design. Combined with the advice from the previous chapter on how to actually dig deeper at a domain model, this might be able to illustrate what a supple design is and how to achieve it.
- Intention-Revealing Interfaces
This is actually similar to a combination of ">Fowler's uncommunicative name and inconsistent names code smell. You can eliminate them by renaming the class, method or interface to something that quickly conveys the intent of the code without forcing the developer to go into the details of the implementation. Also, the new name should be consistent with the ubiquitous language. - Side-Effect-Free Functions
Seems like this pattern should be called Side-Effect-Free Operations since in this section, Evans already defines the term function as an operation that returns results without producing side effects.
Sometimes, certain operations need to be combined together to achieve a desired result. The order which they are combined might matter because each operation might propagate side effects that "prepare" the system for the next operation. For instance, when reading from a file, you have do first open the file, scan to the segment that you want to read from and then start reading to the points where you are interested in. The file stream has a certain state that needs to be taken into consideration. When the order matters, the developer has to take special care in combining those operations, forcing him/her to go into the details of the code to find out the order of doing things.
To solve this problem, you should favor side-effect-free operations i.e. functions. And when it is possible to do so, abstract complex values into their own Value Object.
Ruby has this convention of using the ! character for methods that change the internal state. This makes it more explicit which functions have side effects.1 array = ['a', 'b', 'c', 'c', 'd', 'e', 'e' ] # create array of characters 2 # uniq removes duplication in a array but maintains order 3 array.uniq # => ["a", "b", "c", "d", "e"] without modifying array 4 array # => ["a", "b", "c", "c", "d", "e", "e"] 5 array.uniq! # => ["a", "b", "c", "d", "e"] array is now different 6 array # => ["a", "b", "c", "d", "e"] a
- Assertions
Sometimes it is not possible to use side-effect-free operations especially when dealing with Entities. In such cases, you can help make those side-effects more explicit by placing assertions for pre-conditions, post-conditions and invariants.
Languages such as Eiffel support assertions as part of the language. For languages that do not support them natively, Evans suggests making unit tests to enforce those assertions. So, right now, unit test do not only test for correctness, but they also convey implicit details to the developers. Unit tests are concrete examples that show how to actually use different objects and methods together.
Assertions are also part of the design-by-contract methodology. - Conceptual Contours
This pattern deals with how you should decompose/ compose your components (classes, methods, modules) so that they do not feel fragmented. For instance, making your components very fine-grained might allow for a lot of flexibility in usage, but it also leads to an explosion of details that need to be managed. On the other hand, making your components very high-level makes it easy to achieve common tasks but it becomes hard to do other tasks. For example, doing bit manipulation in Java is hard because it does not have good support for that.
The important thing is to realize how your developers will be using those classes. Java developers do not do a lot of bit manipulation so it is all right to provide them with functions that deal with integers instead of bits and bytes. You find out how your developers use certain components by observing how the system has evolved through refactorings. After various refactorings, you will find that certain components are fairly stable; they are your conceptual contours. However, they are not set in stone. Future refactorings might remove those contours for better ones. - Standalone Classes
Classes that depend too much on one another promote unhealthy coupling. Coupling makes it hard for developers to understand the code and make changes to it since one change could easily affect another class. In the extreme case, some classes should be able to function in isolation.
A good way to detect for coupling, is to check for inappropriate intimacy, feature envy and message chain code smells. Some metric suites might also be able to detect unhealthy coupling between classes. - Closure on Operations
I think this pattern is based on the principle of least surprise. For instance, if I give a method some argument, I would expect that the method also returns something that is conceptually similar to the type of the argument. If I give the argument 1 - anint- to the function increment, I would expect it to return another integer but with the value incremented. It might return adoublebut I would not expect it to suddenly return achar. It's this coherence between the argument and the return values that makes it easy to see what the operation is actually doing.
Some of these refactorings that have been suggested are pretty small and some are more involved. But they do impact the overall readability and maintainability of the code. The most important thing when doing these refactorings is to understand the fact that these are only rules of thumbs. Refactoring is not a one way process in this case. Sometimes, a previous refactoring might work better and you would have to revert back to it.
Evans also metions Domain-Specific Languages in this chapter as a way to tie the model and the implementation more tightly. He cautions that while the binding between the model and the implementation might be better, it might be a maintenance nightmare to keep the domain-specific language in tune with the ubiquitous language. Also, certain languages like Scheme, Smalltalk and Ruby are more suited for domain-specific language than others like Java and C#.
1 puts Time.now #=> Tue May 10 17:03:43 CDT 2005 2 puts 20.minutes.ago #=> Tue May 10 16:43:43 CDT 2005 3 puts 20.hours.from_now #=> Wed May 11 13:03:43 CDT 2005 4 puts 20.weeks.from_now #=> Tue Sep 27 17:03:43 CDT 2005 5 puts 20.months.ago #=> Thu Sep 18 17:03:43 CDT 2003
Domain-Specific Languages should be readable (verbally) and still make sense
Software Development: It's a Religion
Coding Horror: Software Development: It's a Religion:
"But software development is, and has always been, a religion. We band together into groups of people who believe the same things, with very little basis for proving any of those beliefs. Java versus .NET. Microsoft versus Google. Static languages versus Dynamic languages. We may kid ourselves into believing we're 'computer scientists', but when was the last time you used a hypothesis and a control to prove anything? We're too busy solving customer problems in our chosen tool, unbeliever!"
To the list above, we could easily add agile vs. non-agile development process , object-oriented vs. non-object-oriented, etc. Does refactoring code actually improve anything? Is the waterfall process truly abysmal? This was precisely what Paul Adamczyk talked about the other day when he substituted for Prof. Johnson. How much of software development can we actually prove as being more effective? Why do people flock to Process X instead of Process Y? And then why do the people in X flock to W when it comes out?
So why do people do research in software engineering? Well, like religion it's an interesting field. You never know when you will become an apostate your faith. Or how long you will hold on to your dying sect. And on the day of judgment (the release date for software developers) you will truly find out if your God has abandoned you.
Blaine Buxton: Positronic Vibrations:
"As long as the people who have big checks are running on the CLR and JVM Ruby will have to crossover to those platforms to succeed. Business and economics were the downfall of Smalltalk, not natural selection. The 'arrogance of the smalltalk communities sealed the lid'."
As long as everyone plays nice together, all software development techniques, methodologies, tools can coexist together. Hopefully. Passion for something is good but over-zealousness is just plain unhealthy.
For all we know, software engineering might just be the answer we need to life, the universe and everything.
DDD: Making Implicit Concepts Explicit
This chapter was a bit long and I felt that it should be split into two chapters. Overall the contents of the chapter fits well with the title but the first part focused on making iterative steps to refine the model and the second part focused on specifications.
The first part of the chapter mentions some useful methods for identifying missing concepts from the domain. For instance, by paying attention to the daily conversations between developers and domain experts, one might be able to pick up discrepancies in model and the description that the domain experts use. These discrepancies hint at possible concepts that might enhance the model. What else should you pick up from conversations with the domain experts? Pay attention to the contradictions between domain experts. These contradictions might be signs of different approaches to the problem and you should be prepared to accommodate multiple solutions if necessary.
Moreover, you should also not discount the fact that reading a book, a paper or some article on the subject might pave the way for a better domain model. Some domain experts do not have the time to sit through every meeting with the developers. In such cases, it is best to pick up the literature and peruse it. It might also be beneficial to see how other developers have approached this problem in the past.
Another quick way to diagnose potential problems with the model is by checking for code smells. By focusing on the smells between classes, you can identify potential trouble hotspots that might require some refactoring. Of particular interest are smells such as divergent change, shotgun surgery and parallel inheritance hierarchies since those smells affect a large portion of the source code.
Refactoring toward a breakthrough is a hard enough process. But actually finding the end goal for refactoring to can also be complicated. What Evans has done is present a list of rules of thumb and illustrate them using concrete examples. Even with these guidelines, Evans does not fail to emphasize the importance of trial and error. Sometimes, it takes multiple session of trial and error before getting the right model. Experience certainly helps but I would assume that a great deal of perseverance and an excellent version control - for reverting code - helps significantly as well.
In the second part of the chapter, Evans focuses on the Specification pattern. The Specification pattern is a predicate that determines if an object does or does not satisfy some criteria. By making this predicate its own object, it becomes clearer to people that this is requirement rather than some arbitrary behavior of the object or model. I felt that this was the main theme of the chapter: making implicit concepts explicit in an intention-revealing manner.
A Specification object takes care of handling the testing of whether some criteria have been met. There are three main uses for specifications: validation, selection and generation. These three uses are on the same conceptual level.
Specifications should not be confused with Assertions (presented in the next chapter) which ensure that certain pre-conditions, post-conditions and invariants are maintained through the program. Use a specification to test if an object fulfills some criteria that is part of the domain logic but use an assertion to make explicit the side effects from calling some procedure. Specifications and assertions both make the intent more explicit but they do so for different purposes. Used together, both can communicate the model better.
DDD: Breakthrough
Martin Fowler made the term refactoring popular. His book Refactoring: Improving the Design of Existing Code offers micro-refactorings that help reduce the stench of the code smells in the code. These micro-refactorings can usually be automated as exemplified by increase of automated refactorings in popular IDEs. Taking it up a notch is the concept of Refactoring to Patterns introduced by Joshua Kerievsky in a book of similar title. Refactoring to Patterns describes the process of modifying existing code to embody established design patterns. I am halfway through that book and it does offer good linkage between design patterns and refactoring.
Nonetheless, Evans wants to "kick it up a notch" (to quote TV's celebrity chef). The refactorings he wants to achieve not only make the code more readable, but it is also offers the "aha" feeling to the design when it is done right. This is the breakthrough. At this point, not only is code being affect, the entire model might be affected as well. This is a change that will ripple through the entire system. Thus, it is a costly change that can take weeks (not hours or even minutes like the micro-refactorings). But, with its cost, comes greater insight into the design and model more suited to address future requirements (or handle existing one better).
Figure 1: Returns from refactorings are not linear
This chapter presents the motivation for refactoring the domain. While this chapter ended with a happy ending, I will not be surprised if some refactorings had dire consequences instead of beneficial ones. Nonetheless, those dire consequences will open up new insights that can be used in a future iteration.
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.
