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!