Talks

Workshop: Applications First, Frameworks Second - Better Systems through Design

Workshop: Applications First, Frameworks Second - Better Systems through Design

by Adam Hawkins

In the workshop titled "Applications First, Frameworks Second: Better Systems through Design," Adam Hawkins presents a detailed approach to designing Ruby on Rails applications with an emphasis on maintaining separation of concerns and applying design patterns. The session addresses the common pitfalls experienced when using Rails, emphasizing that the framework should act as a delivery mechanism rather than dictate application architecture.

Key points discussed include:

- Introduction to the Workshop: Hawkins highlights the importance of understanding business logic and proper application structure before engaging with Rails. The workshop is based on his previous writings, particularly on hexagonal design and domain-driven design.

- Concepts of Design Patterns: The workshop introduces several design patterns including service objects (also referred to as use cases), form objects, and the repository pattern. These patterns help organize the business logic behind the application, allowing for greater flexibility and maintainability.

- TDD Approach: Throughout the workshop, a test-driven development (TDD) approach is reinforced, where small tests are written to guide the development of the application, ensuring that the underlying logic is solid before implementing it into the Rails framework.

- Incremental Steps: The session is structured into small, manageable steps that build upon each other, each introducing new concepts while encouraging participants to interact and solve problems collaboratively.

- Use Case Examples: Hawkins discusses practical examples such as a simplified post object to illustrate how complex business logic can be cleanly handled using the discussed patterns. Examples of validation handling and error management are also provided to reinforce the design principles.

- Connecting to Rails: Towards the end of the workshop, Hawkins explains how to integrate the application design principles into the Rails framework, showcasing how to use Rails as a delivery mechanism rather than allowing it to drive the design.

In conclusion, Hawkins emphasizes that developers should focus on the application's core logic and structure before implementing frameworks like Rails. By following the patterns and processes outlined, developers can create more robust and maintainable systems. The key takeaway is that thoughtful design and proper application architecture can significantly enhance the development process, reduce errors, and increase productivity in Rails applications.

00:00:16.060 So, how many of you guys have had a chance to actually know about the RailsConf tutorials.com? Can you raise your hand? Okay, so just a few people.
00:00:31.230 To reiterate, before we get going, you should have Ruby 2.1 installed on your machine. If you don't know how to do that or don't have it, please ask somebody next to you. Secondly, at least have the first repository cloned from this slide, 'Applications First, Frameworks Second'. You can also clone the Chassis gem onto your computer; we'll be using it later. Just in case you cannot connect to the internet, you can point it against a local install.
00:00:55.440 Alright, let's get into it. My name is Adam Hawkins, and I'm running this workshop called 'Applications First, Frameworks Second'. The content of this workshop is mainly derived from a blog post and things I’ve been working on over the past year or so, which are linked on the RailsConf tutorials.com website. Maybe you have heard about it, maybe you haven’t.
00:01:14.250 The blog series is titled 'Rediscovering the Joy of Design', which focuses on hexagonal concepts, domain-driven design, and separation of concerns. I believe these are desperately needed in the Ruby community itself. This is the first time I've done a workshop with this many people. I have presented this content multiple times before, including to a group of Java developers with no Ruby experience as an alternate introduction to Ruby on Rails.
00:01:43.220 Today, I will present the same material to a group of probably well-experienced Ruby and Rails programmers. I hope that at least some of you, maybe most of you, have experience working with Ruby outside of Rails because that’s what we’re going to do today. I hope that’s not scary; it’s what we’re going to cover. This workshop will mainly go through a set of small predefined steps I’ve created in the repository that hopefully everyone has cloned by now.
00:02:34.319 We’ll go through, I think, twelve or thirteen steps, depending on how the time goes. Maybe we’ll do some live coding based on what you guys want towards the end. The repository contains a branch for each step, named like feature/step-1, feature/step-2, or similar.
00:03:11.780 When we get going, I have slides encouraging you to do the particular exercise in each step. If you like, you can also look at the appropriate pull requests on GitHub to see the diffs. However, I encourage you to try the steps on your own. You can look at my solution or follow along in your own branch if you’d like.
00:03:47.270 The workshop will continue based on the diffs in my pull requests. If you haven’t checked out the code on GitHub yet, on the 'master' branch is where we will end, but that’s not where we will start. How many of you were able to read some of the content I posted in the prerequisites?
00:04:08.780 Alright, let's start here. How many of you attended DHH's keynote? I think most people. If you connected strongly with that keynote, then this might not be the right place for you. I say this not to scare you away, but to be honest with you about what you will get out of this workshop.
00:04:35.000 These are things that I deeply care about and have experience working with. I think they are important, but members of certain areas of the community may consider these ideas over-architecting or might call it architectural astronauting. I have applied these concepts and patterns over the course of a few years, and they have been very successful when applied to problem domains of certain complexity.
00:05:08.750 So if you’re interested in learning about these things, great! But if you are interested in working on smaller problems and don't necessarily care about doing the whole TDD thing, then I want to be upfront with you: you probably will not enjoy yourself here.
00:05:32.420 This blog series discusses the ideas of hexagonal design, domain-driven design, separation of concerns, and creating boundaries around larger parts of your application. I don’t know if any of you attended the earlier talk about views, presenters, and decorators, but they discussed DHH's comments on service objects.
00:05:50.810 DHH stated he believed service objects were a complete waste of time, as he had never seen a use case for them in the wild. However, I can tell you that they do exist, and I use them with great success. The blog covers how to correctly arrange some of these objects.
00:06:29.460 In terms of terminology, you may have heard the term 'service objects.' I prefer to refer to them as 'use cases' since that's more descriptive, but since the vernacular during this conference has been 'service objects,' we will use that. A service object is something that actually does what the system is supposed to do—it handles user interactions.
00:07:00.470 It takes input from what I call a form object, which is responsible for modeling user input. The service object uses that input to perform operations. My current approach also involves using the repository pattern, creating a real separation between the domain models themselves and how they are persisted.
00:07:37.820 How many people here are familiar with the repository pattern? How many have actually used it in practice? I hope you will learn how to at least implement some of that today.
00:08:01.370 Unfortunately, to demonstrate these concepts accurately, we will need a relatively complex problem domain that cannot fit in one workshop session. Thus, the things we’re going to do here may seem overly architected for this small problem domain. If you think that is over-architecting, then you are thinking in the right direction; if not, then you may want to rethink your approach.
00:08:43.200 This workshop aims to demonstrate a certain way to solve problems so that you can use these practices on larger problems. I don’t know how many of you have built an application completely separate from Rails—whether that be a web app, console app, or even a gem—but for many, the initial concept can be hard to pin down.
00:09:00.060 If you have troubles thinking like that, then hopefully this workshop will provide a good exercise for you. I’d like this to be an interactive experience. Don’t hesitate to ask questions of each other or me. If you would like to work in pairs during this workshop, I highly suggest that. It might help cut down on bandwidth issues.
00:09:35.600 We are going to do something very simple in a domain that we all understand. We will work with a single model, and that model will be a simple Post object. People in the Ruby community often seem scared of patterns, perhaps having been burned by Java or similar experiences. However, do not fear patterns. I will expose the use of patterns on problem domains of certain complexity, and you will see that they make a significant difference.
00:10:04.920 This workshop will be entirely test-driven from the very beginning. We will use service objects, form objects, and a gem called Chassis to help organize things outside of the hexagonal architecture. We will utilize Active Record for persistence, which will help us reach our end state. We also plan to use Action Pack to build this application online, to ultimately serve our application to the user.
00:10:41.990 Now it's time to get started. If you can open your terminal or command line, just check out 'step-one' or your own branch from the very beginning. We will begin from a clean slate, setting up the project structure. Running tests correctly from the initial commit will be our first challenge. There is a particular git command to check out this step and return to the initial commit.
00:11:29.200 Once you do this, you should have a clean slate, and your job will be to set up the rake file so you can run tests. A hint for anyone who has never set this up: you will want to use the rake test task to run a set of test files. Throughout this workshop, we will utilize tests as we will be relying on a built-in library with nothing else to install.
00:12:14.690 At this juncture, you might find yourself in a detached head state. My mistake coming from the planning process; however, after this point, it will get easier. If you are interested, you can verify your setup and compare it to solutions found in my repository.
00:12:42.490 Now, we can move on to the next part of the workshop. If you haven't figured out how to set up an initial test yet, that’s no big deal. This first part is mainly boilerplate code, included to familiarize you with setting up the infrastructure to get the application running.
00:13:18.150 Let's continue. This step requires us to introduce various concepts at the beginning, which we may not fully understand initially. We'll need to create a 'system-level test' as discussed during DHH’s keynote.
00:13:59.700 For this step, we need to set up an integration test that checks if someone can submit input to our system, which goes all the way through the process and ultimately checks what comes out. We will create a service object, a form, and a repository, along with a model class. You may find these steps challenging, especially if you are required to write a blank test that sends data for processing and checks assertion verifications.
00:14:59.490 This integration test should ideally encourage you to write a test case that checks if we try to create a post and that the hash of title and text is processed. You might get something that interacts like how you have built your applications with Rails, which is perfectly acceptable.
00:15:44.090 To get things rolling, write a simple test to verify that a post can be created using a hash with a title and text. Notably, you will need to incorporate a service object that functions as the public interface, a form object that accepts input, and a repository that represents the object collection.
00:16:30.090 To reiterate, you have to distinguish between the various responsibilities as you establish the domain. There will be a service object to handle the relevant functionality, a form to manage the input, and a repository for managing the collections of objects. When creating this, organize how they interact with one another.
00:17:24.440 Next, I will demonstrate how my solution works without showing you the code. Initially, you will have a failing test because nothing has been defined. We will continue iterating through this until we achieve a passing test. If you have any inquiries regarding the repository pattern or its practical application, feel free to raise them.
00:18:08.640 The repository pattern creates an effective separation between data access and how the models are stored at a low level. As for our implementation, we do not have a specific database yet. The goal is to create an interface to access these data points and implement them in different ways.
00:18:42.690 Now let’s pull up the diff for this step to discuss it. Essentially, we have a class called Post Repository that interacts with the data store. Additionally, we'll create appropriate instance methods for creating, querying, and managing all posts. Once done, we will organize the test cases pointing accordingly.
00:19:08.970 The class we name PostRepo will act as a facade to interact with whatever data store we implement, initializing it with relevant data to create instances. The essential process of manipulating the records includes asserting changes before and after expected interactions occur. This means we can execute our tests to confirm that data is successfully being added to our data repository.
00:19:49.920 Juggling through these design patterns brings us face to face with performance issues, as you might find in languages that do not require much boilerplate manipulation. However, the benefits of having an array of design approaches help clarify your thought process during interactions.
00:20:34.160 We will now navigate to the next phase of our implementation. This step emphasizes understanding how the repository interacts with the rest of our domains and making sure all integrations connect correctly because ultimately, if invoked methods do not process, we will have errors.
00:21:00.960 Throughout this session, be mindful of how we define behavior relevant for the instances we are creating. Having those iterations help craft a space for assessing whether or not the initial header you created works.
00:21:50.120 Consequently, active_record becomes a significant portion of our conversation in this sense. Its role as a gateway provides us a framework within which we can process data flow and validate dependencies on every layer of the application. At this point, expect a flurry of integration between our repository and classes that is necessary to validate data rather than being unduly concerned with the exact implementation.
00:22:50.470 A significant feature of this session involves handling nested data to ensure everything aligns while maintaining appropriate separation. Many organizations' practices emphasize the need for consistency as clients request validation for larger objects.
00:23:38.230 For example, if options exist where you deem it appropriate, collaborate to pull additional data. APIs regularly utilize validations that alter data after basic interaction protocols. This becomes significant as users might anticipate certain responses to these data modifications.
00:24:25.540 Let's pivot slightly to verify if you maintain an understanding of separating concerns while ensuring interactivity supports your development. Validations could be context-free or context-bound, and you’ll want to think about how your applications determine correct behavior in practice.
00:25:11.060 As we continue, please keep in mind the essence of isolating concerns and encapsulating data access and integrity into logical components, systematically verifying the behavior intended through your application architecture.
00:25:41.330 In defining your application’s behavior, allow for creative approaches such as separate policy objects around business rules while maintaining simplicity in anything pertaining to individual data relationships.
00:26:29.290 When confronted with how many relationships your application maintains, understand that your use cases will contextualize how and when each relationship's informational output must be accessible, so maximize opportunities to control routing behavior.
00:27:01.390 Reviewing the components in your repository can contribute to where you're directing the user, so organizing data flow within your application dynamically translates use cases into practical guidance or behavior specifications as needed.
00:27:46.940 By focusing here, you can instill a pattern that observes behavior principles internally while highlighting the overall architecture. Ensure clarity in each test case while incorporating functionality into each pattern.
00:28:34.530 Overall, what we can affirm is your frameworks and data repositories must evolve with the application as it scales. Simultaneously, you have opportunities to manage complexity framing responsibility around system management in a respective organization—collating subdomains anchored on successful transformation.
00:29:20.890 So, feel empowered to echo these statements as they reflect not only your design philosophy but also the dynamic pathways you've created through trial and error over time learning about data interchange and user journeys.
00:30:09.450 Finally, as we round this discussion, remember the reflections of active management around responding structures to make consistent and reliable connections between your architecture and performance. Develop documentation that enhances understanding as those intended interactions compromise the layering purpose of various contexts.
00:30:53.670 Above all, feel encouraged to explore further paths in designing interactive applications where flexibility adapts strategy according to user-interactive data requirements. With that being said, we have around 15 minutes left to delve into some refinements moving forward.
00:31:37.170 It's crucial we now move towards ensuring your individual contexts integrate through structure patterns enhancing reliance on quality and clarity. Breakout scenarios illuminate opportunities collectively determining pathways where processes don't hinder speed.
00:32:25.890 The objective is to lift each iteration to introduce you to usability principles around functionality that meet performance benchmarks yet resonate with complex conditions in real-world applications.
00:33:03.110 Understanding collaboratively displayed frameworks ensures troubleshooting strategies remain robust while allowing for the exploration of relational datasets across integration touchpoints.
00:33:49.290 So let’s take this opportunity to ensure all your questions generate meaningful solutions together here and now. Reflect on how individual insights contribute to a larger problem-solving framework.
00:34:28.830 Please feel free to reach out if you have queries needing clarification or pathways that require more exploration encompassing user experience across data integration.
00:35:06.160 Thank you for joining and contributing throughout this workshop. Your engagement enriches all levels of understanding around effective software architecture, and I look forward to seeing how you implement your learnings moving forward.