RailsConf 2014

Domain Driven Design and Hexagonal Architecture with Rails

Domain Driven Design and Hexagonal Architecture with Rails

by Eric Roberts and Declan Whelan

The video titled "Domain Driven Design and Hexagonal Architecture with Rails" features Eric Roberts and Declan Whelan discussing how to effectively apply design patterns in Rails applications to avoid common pitfalls like the "fat model, skinny controller" problem.

Key Points Discussed:

  • Introduction of Speakers: Both speakers share their backgrounds, emphasizing their passion for coding and maintainable software. Declan, the CTO at Printchomp, describes his journey in adopting Ruby on Rails despite initially lacking expertise in the language, while Eric, a software developer at Boltmade, highlights his transition from front-end to back-end development.

  • Challenges in Rails Development: They discuss the typical structure of Rails applications, where the responsibilities of controllers, models, and views often become blurred, leading to complex and unmanageable code. They showcase an example of a 90-line method that encapsulates too much functionality, indicating the need for better organization.

  • Domain Driven Design (DDD): Emphasizing the importance of understanding the domain of the application, the speakers explain DDD as a way of tackling complexity by creating a common language between domain experts and developers. They discuss using classes named after domain concepts such as "Customer" and "Product" to enhance clarity and reduce miscommunication.

  • Hexagonal Architecture: They introduce hexagonal architecture as a means to structure applications with a strong focus on domain logic. Layers like application-level code and various adapters connect to this core domain, emphasizing that different concerns (like database operations) should reside separately from business logic to promote more manageable codebases.

  • Practical Patterns:

    • Form Objects: Presented as an approach for handling complex form submissions where multiple models are involved, aiding clarity in controllers.
    • Request Objects: Discussed as a method for encapsulating request data and validations at the boundary of the system, helping to streamline controller logic.
    • Service Objects: Introduced to handle business logic cleanly, allowing controllers to delegate responsibilities effectively.

Conclusions and Takeaways:

  • The speakers conclude by emphasizing the fun in understanding and improving complex systems through experimentation and breaking responsibilities into smaller parts.
  • They stress the need for Rails developers to look beyond their framework and familiarize themselves with broader software design principles that can be applied across different languages and platforms.

The presentation encourages developers to adopt best practices in software design to create more maintainable and extensible applications while recognizing the inherent complexities in software development.

00:00:17.520 All right! Hi, I’m Eric Roberts, and I'm here to talk to you all about how you can use software design patterns to put your Rails app on a diet and make your tests run really, really fast.
00:00:36.040 Jokes aside, we will discuss some design patterns—not just focused on making your tests run fast. We are here to talk about Domain Driven Design, Hexagonal Architecture, and Rails. As I mentioned, I’m Eric Roberts, a software developer working at a company called Bolt Made in Waterloo, Ontario, Canada.
00:00:48.039 I met Dean when we worked together, and previously, I was a front-end developer for a number of years, mainly focusing on the view layer of Rails applications. However, Dean dragged me kicking and screaming into backend development, which made me care about the things we will discuss today.
00:01:03.600 This is the largest audience I’ve ever presented in front of, so if you'll excuse me, I need to take a picture to send to my mom. Ok, hi everybody! It's a real pleasure to be here. My name is Dean, and I’m the co-founder of a company called Print Chomp.
00:01:15.479 My story goes back about two years when we had the opportunity to launch Print Chomp. I was exploring technologies and determined that Ruby on Rails was the best platform for us. The only issue was that neither I nor anyone on my team was familiar with Ruby or Rails, which was a bold, if not risky, decision.
00:01:33.520 However, it’s a decision I don’t regret. One of the things that attracted me to the Rails community was its spirit of sharing and openness, as well as the strong focus on testing, which has been critical for me. Interestingly, we intentionally took on a lot of technical debt during our development phase, as I knew I wouldn’t understand our domain well since it involved printing, and I also didn’t know enough about Ruby or Rails.
00:01:58.880 We decided to execute the best we could while accepting that we would accumulate significant technical debt. And as you might expect, that turned out to be true.
00:02:14.200 Recently, we had an opportunity to build an API, which excited me. I realized I faced two challenges that needed to converge. First, I had logic in our application that I needed to share in our API. Meanwhile, our code was scattered throughout various parts of our controller and model logic.
00:02:37.200 Secondly, I needed a strategy for eliminating the technical debt that had accumulated. I turned to Domain Driven Design and Hexagonal Architecture. I want to share what we've learned throughout this journey and where we’re heading.
00:02:57.200 To make this presentation more interesting, let’s think about one key takeaway. There’s complexity in the software that we build and in the problems we solve. We need to embrace that complexity and tackle it head-on. By doing so, we will find more joy and satisfaction in our work. It's not just about making a system functional; it's about truly understanding our domain, modeling it, expressing it in our code, and making the code as expressive as possible.
00:03:36.000 I also realized that while I knew our code was messy and identified some refactorings, I struggled to envision what a significant refactor would look like. How would the shape change? What would the namespaces become? What would the classes do? Domain Driven Design and Hexagonal Architecture helped me visualize and communicate those ideas with my team.
00:04:14.480 Another point I want to emphasize is that there’s much more to being a Rails developer. The concepts and patterns we discuss today are not exclusive to Rails; they apply across various platforms, whether it be Node.js or desktop applications. Familiarity with these concepts will allow you to transfer your skills to different technologies.
00:05:06.760 Now I want to ask Eric to walk us through some of the pains we experienced during our initial Rails development and see if it resonates with you. So, everyone knows what this is: it's a Rails folder structure! It's an excellent starting point when you begin working with Rails, as you have logical areas for controllers, models, and views.
00:05:34.000 However, what happens when your code’s responsibility doesn’t fit neatly into MVC? We found that various responsibilities started getting blurred. When code doesn't fit nicely into controllers, models, or views, you end up throwing it somewhere, which results in chaotic growth.
00:06:07.680 As things expand, the lines between responsibilities become blurred, making it hard to extract reusable components. You can end up with methods that are too large and complex, like this 90-line method I have on screen. It’s not even from Print Chomp but from another project, focused on setting prices for properties based on date ranges.
00:06:50.480 The challenge is that nobody sits down planning to write a 90-line method. These things grow organically, often starting from much simpler specifications. To add a feature, the next developer might say, ‘I’ll just add a little bit more code,’ which can lead to a tangled mess.
00:08:00.760 And instead, we often end up creating overly complex solutions, like Rube Goldberg machines for simple tasks. Thankfully, there are well-known design patterns to help alleviate these issues.
00:08:29.680 We will touch on some of those patterns, but the goal is to simplify and break down this complexity. Once you've extracted these responsibilities into smaller, manageable components, you might find yourself asking: what do I do with all these parts? It’s important to focus on Domain Concepts, services, and entities at the core.
00:09:01.040 The surrounding layers of your architecture—pathways to the database or the web, like APIs—should be lesser concerns, allowing you to zero in on the core functionalities of your application. Dean will discuss Domain Driven Design further.
00:09:42.120 Many of you might have heard about Domain Driven Design, so I’m curious, how many have intentionally used it for their projects? This design approach may seem daunting at first, especially as introduced in Eric Evans’ book from 2005, which is great but difficult to digest.
00:10:38.240 It’s perhaps the only technical book I’ve read twice! While some concepts are challenging to grasp on the first try, we will post our slide deck and references to materials that may be easier to read and understand.
00:11:11.279 What Eric Evans highlights is that we need to tackle complexity by understanding the domain, which includes the business rules that govern our systems. For instance, if we’re going to implement sales tax, we need to understand the specific rules around that.
00:11:49.680 The goal is to encapsulate these rules inside our domain so that our outer layers act as relatively thin facades, enabling the reuse of logic across APIs and various applications. We want to avoid duplicating business rules. Evans suggests achieving this through a concept called ubiquitous language.
00:12:39.600 Think of the Tower of Babel, where everyone spoke different languages, making communication complex. I once worked on a financial transaction system where the previous developer used a model train metaphor for transactions. It was challenging because I had to translate that train speak into code.
00:13:32.560 Thus, we want our domain experts' language to flow directly into our code. For example, if a domain expert uses the word 'customer', there should be a corresponding 'Customer' class in the code, minimizing translation loss between domain language and code.
00:14:03.200 Next, let’s briefly touch on some key patterns from Domain Driven Design. These patterns are not merely academic theories; they’ve emerged from practical software development experiences. Knowing a concept like 'value object' allows for richer discussions around code.
00:14:43.400 Now, transitioning to Hexagonal Architecture, this approach is about keeping your core domain at the center, surrounded by application-level code that implements the respective application rules. Following this architecture, we utilize various adapters to connect different interfaces to our core.
00:15:34.060 I want to discuss specific patterns, including form objects, request objects, and service objects, starting with form objects. When using Rails scaffolding, forms are commonly instantiated directly from active models. However, when a direct mapping isn’t feasible, instantiating a form object allows for improved flexibility.
00:16:20.120 For instance, we created a class for ticket forms, including validations just like we do with active models. By doing this, I can cleanly handle data from my nested attributes without the messiness that usually comes with that pattern.
00:17:02.600 Now, let’s touch on request objects. This is yet another technique for pulling complexity out of controllers and models into a request object to simplify operations further. The idea remains to keep request validation at the boundary. This way, the complexities of these operations don’t leak into the core application code.
00:17:37.720 Service objects stand out as a method for extracting procedural code away from controllers. They encapsulate the logic related to specific actions, like creating an order. The controller is simplified to just invoking these service methods.
00:18:12.760 These patterns make Rails applications more manageable, but what’s the elephant in the room? Getting these concepts to work seamlessly with Rails, particularly with Active Record, can pose significant challenges.
00:18:59.039 The repository pattern is a core component in managing the interaction between our domain objects and Active Record. Successfully implementing it involves transforming a domain object to an Active Record object and back.
00:20:05.920 I developed a simple save method using mappers to handle this translation. My goal is to maintain separation of concerns, ensuring that persistence logic does not clutter domain logic.
00:20:51.840 In addition, we leverage the identity map to keep unique instances of each entity during an API request—ensuring that each one only exists once per request. This element serves as a basic caching mechanism while fulfilling its primary function.
00:21:48.560 Moving forward, many of the concepts we’ve discussed like service objects and request objects fit well into our application’s architecture. At the same time, I’m keen to refine the repository pattern, distinguishing where to place these components in Rails.
00:22:49.960 While Rails doesn’t explicitly guide you on where to place service objects, you can make a services directory and Rails will be able to find it without any complications.
00:23:55.760 Now, what’s the main takeaway here? Embrace complexity! Getting a solution to merely work is only the initial step. The excitement of programming thrives in iterating to improve that initial draft.
00:24:47.080 We want to build out our systems to the point where we enjoy revisiting the code—and that involves breaking it down using the patterns we've discussed today. Know where you're going in your development. As Lewis Carroll said about Alice’s adventure in Wonderland, if you don’t know where you’re going, any road will get you there.
00:25:45.440 It’s important to choose a well-defined path in your development journey. These ideas have helped us, and hopefully they’ll be beneficial for you as well.
00:26:39.480 Lastly, as Dean mentioned, we have a GitHub repository that we’ll populate with code examples shared today, particularly around the repository patterns. We encourage the community to engage in discussions through issues and pull requests.
00:27:05.200 I’ll include resources to some of the books that have influenced us in our journey with these ideas. Thank you all for your time!