I clearly remember my first Rails application years ago. Plenty of scaffolding, bunch of methods in models and tuned HTML templates. It worked.
Of course, for building something real and complex it wouldn’t work at all. If you grow such a project, you would end up with highly coupled domain logic and framework specific code. As side effect complexity of objects is high, tests are very slow and domain code is unclear as a result of many compromises done to “fit” domain model to the framework code. That all slows project down and ultimately kills productivity.
Coupling domain and web framework code
Rails MVC is just a basic layering. On top of that, having “fat models and thin controllers” was the first step, which allowed the controller to become clear (handle request, call some models methods, prepare and render views). Models became too “fat”, though. At start I approached this situation by extracting models from the class and including those back. Its of course false refactoring and only outcome is that all the mess is splited in multiple pieces.
One of the biggest problems is the lack of clear separation of major concerns in the application. Typical complex system has plenty of domain objects and many use cases, which manipulate objects in a given workflow. At the end results are stored in the database. Most of this responsibility gathers in models and violates SRP (Single Responsibility Principle). Processes are not clear, because they are not explicit.
Such an app had well normalized database tables and correct set of primary and foreign keys in database, although the domain code just roughly represented the real world model. A lot of key details and objects were missing. Its price of designing the code around Rails models. Extracting bunch of modules would barely help.
The right way
When I started to build Sniffer I wanted to do things right so I wouldn’t get into troubles in later stage when project grows. I did 3 major decisions about system code:
- Domain would be written in plain ruby
- Focus on domain code, having all the other concerns as secondary
- Minimize the interaction between domain and database or web framework
The high level view would look like this:
Domain is clearly separated from the rest of codebase. All the services (use cases), domain models, value objects and any other domain specific code is there. Rails models are left with just single responsibility. Persist data into database.
Those turned out to be incredibly important. Lets discuss couple of benefits I got from it and a bit insight why.
Low coupling. Object oriented programming was invented to represent user mental model of the problem. It can barely be done though, if you need to do compromises here and there about how to write a code. Having your domain in pure Ruby allows you do it right. The outcome is minimal coupling between domain code and web framework / database system. All the domain concerns are explicit, thus code is better readable.
Flexibility. As you develop your code without any concrete framework in mind, you are free to choose any of them later. Even if you go for one and wanna switch to other, it should be possible without affecting your domain code at all. The same goes for the database system. If you have clear abstraction and API between domain and ORM or database driver, it becomes fairly trivial to switch. In case of sniffer I did try CouchBase, Redis, Riak and ended up with CouchDB. I had pretty simple persistency abstraction with bunch of functions to store hash or array. Implementing it in different databases was matter of hours. It can barely be done in case domain would contain ORM code directly.
Note: You can say well this is not really performant. Thats true. Although plenty of optimizations can be done in any stage of project. But I can decide that later comfortably.
Fast spec suite. Domain specs are blazingly fast. Because of persistancy abstraction I was able to create in memory database storage, which is used for most of the specs - I dont really wanna test that database works for each domain spec. There is special test for that. Instead I have few hundreds of specs running in seconds. No database is started, no framework is started, just plain Ruby.
Clear domain model. One of the biggest benefit I see is that domain model really closely represents the real world. Having the domain as the primary focus in code allowed me to create right classes, more naturally. As outcome, if the model represents world precisely it fits into user head, so implementing new features would be way easier. You would see new features can be done easier without need for significant refactoring.
Using rails scaffolding, which heavily focuses on persistency it would be way harder to have a right domain model. Keep in mind that domain model and Rails model are not the same thing.
In last year I’ve studies a lot about software systems architecture, mainly from guys like Martin Fowler, James Coplien, Uncle Bob Martin or Erich Evans. They talk about separating changing things code from stable one. Separating behaviour of application from the domain itself. To separate domain from the frameworks and database or to let domain be the core of your codebase. Each has a bit different way to say a very common thing:
Focus on your use cases and craft them explicitly in domain code. Keep the domain code as most important and well separated from the rest of the world. Everything else is secondary.
After coding few months, following this way I really believe its the way to craft stable and maintainable code.
6 months ago
blog comments powered by Disqus