GoRuCo 2013

Putting off Persistence

In Rails, we have a beautiful framework that can take us from a blank slate to a fully-functional app in little time. However, doing things "The Rails Way" has a lot of implicit dependencies, including persistence. Are you really equipped to make one of the largest decisions about your app before any of your code has even been written?
By putting this decision off you can get a feel for your domain before ever committing anything to a db schema. This means fewer migrations and fewer db-related hacks. Also, these practices encourage encapsulation and interchangeability, which means you get the ability to choose which datastore is best for you. Not just on an application level, but on a per model level as well!
During the talk we'll be walking through simple examples at different points in application lifecycle. These snapshots will address the biggest pain points of a persistence free process and leave you in a position to put off persistence in your own app development!

Help us caption & translate this video!

http://amara.org/v/FG96/

GoRuCo 2013

00:00:11.480 Thank you! Hi, guys. My name is Lauren Voswinkel. You can find me on Twitter as LaurenVoswinkel and on GitHub as Valerissa. I work for Gigapan Systems, where we make robots that help you take really awesome pictures. This began as a collaboration between CMU, Google, and NASA to stitch back images that returned from the Mars rovers, Spirit and Opportunity. So, anything you saw around 2005 or 2006 from Mars was probably part of what eventually became Gigapan Systems. That's pretty cool!
00:00:18.300 However, I'm not here to talk about that. Before I discuss putting off persistence, let me mention that my slides are up here. If you can’t read the code on the screen, feel free to grab the slides and follow along on your laptops.
00:00:37.260 Today, I want to discuss a topic I never really considered in my development process—persistence. When I worked on any side projects with Rails, I typically followed the standard approach of starting a new Rails app, running some generate scripts, and creating a bunch of models tied to Active Record.
00:00:45.780 This approach is excellent because it helps you get up and running quickly. However, the downside of speeding through the process is that you often overlook critical decisions. This oversight can lead to complications, especially as your app evolves.
00:01:03.059 Anyone here who has started a Rails project using Active Record might have thought, "I’d love to switch to NoSQL for part of this," only to find themselves overwhelmed by the complexity down the line. You might start the transition with good intentions, promising yourself you’d be done in two weeks, only to realize that you’re nowhere near meeting that timeline.
00:01:21.420 It’s a painful process because of how tightly intertwined our projects are with Active Record. This leads me to an important concept: take some time to think about your big decisions, or better yet, delay making them as long as possible. Sandy Metz has said in her talks, "You will never be as ignorant about your project as you are right now." It’s a fact many of us take to heart.
00:01:40.020 So, why in the world do we make one of our biggest decisions—our database—right from the beginning? Your database choice will dramatically impact how your project grows, how easily it will scale, and how you model your data.
00:01:52.320 In my personal experience, developing features of an application before committing to a datastore is one of the biggest advantages you can have in your process. But before diving deeper, let's take a look at some history.
00:02:05.880 I tried building an application to manage events multiple times using the typical Rails way. I went through generate scripts and ended up with 30 migrations in the first week, but it just didn’t work. Eventually, I decided to wipe the slate clean and follow along with Corey Haines' first episode of building an app.
00:02:25.740 In that episode, Corey introduced some fascinating concepts for developing an application, and the first part did not involve using persistence at all. I was following this process iteratively and making excellent progress until the episode concluded, leaving me wondering where to go next.
00:02:40.740 So, I took this idea of putting off decisions to an extreme. Here’s the basic scenario: I wrote simple Cucumber scenarios. They were very basic and involved defining events, such as 'There are no events to display' or 'There are some events to display.' I checked those and ensured the expected output matched.
00:03:02.760 The descriptions were straightforward: visiting the URL would show the message, 'There are no events listed at this time.' This followed behavior-driven development principles, and this is where it got interesting.
00:03:17.940 In my process, I developed with as little code as possible to satisfy the tests. I had an event that had a status displayed, defined in the controller class but outside of the controller itself. Then, inside the controller class, the events collection was just an object with three methods: upcoming events, current events, and past events.
00:03:45.359 These methods took a block, which defined how to display each event. The event view was quite simple, showing upcoming events, current events, and past events. Then I thought, let's add the concept of filtering by dates. This was where I first encountered a roadblock, as I needed to extract information from the controller and create a model.
00:04:04.900 I was very hesitant about using the generate scripts, so I decided to create a normal, plain old Ruby object instead. However, I quickly ran into a problem where I couldn't access the information in the view scope from the model.
00:04:19.740 I realized I needed some way to persist that information from the model to the controller and then into the view to be checked by the test. My solution? I simply made an array. It wasn’t perfect, but it allowed me to overcome the initial barrier.
00:04:34.680 Every time an event was instantiated, I pushed it onto a class variable array. The key code here was the 'self.events' method that instantiated an empty array if it didn't exist. If it did exist and no parameters were given, it would return that array. When provided with a parameter, like a time range, it would filter using a 'select' on the array.
00:05:04.320 If given a block, it would iterate over the list, allowing interaction with the code. I also pulled out the scoping methods—upcoming, current, and past events—into this class. At this point, nothing changed, and it continued using the same class-level instance variable.
00:05:20.400 The big change came when I pulled out the code that established what an event was and the associated methods. Now, all I had to do was create a date range when parameters were provided in the URL. If they weren't, I sent 'nil', returning the entire array.
00:05:42.360 The view remained unchanged because of the principles of behavior-driven development, allowing me to refactor extensively in the backend without any impact on the view. However, I realized that I was reinventing the wheel, considering that ORMs provide most of this functionality out of the box.
00:06:05.880 My motivation was the fact that an ORM, packaged with connections to your domain model, can lead to leakage of encapsulation, creating connections and interactions without strategic thought. I often see more violations of the Law of Demeter through ORMs than by any other means.
00:06:37.200 One significant benefit of my approach is that it forces you to craft an internal API governing how your objects interact. Every method must be purpose-built, which encourages thoughtful design. While this may feel verbose and cumbersome, it provides valuable insights into your process.
00:07:02.920 Pain points become more immediately evident as you observe how your methods interact, which is beneficial because it's like a warning flag indicating potential issues early on. This process fosters awareness, helping you to avoid issues that would emerge later.
00:07:24.680 In this project, I left the code managing events pushed onto an array. Then, I added a Cucumber feature that ensured the displayed events only fell within a specific date range. This simple check proved effective, confirming that the right events were visible when expected.
00:07:56.700 The next challenge was defining what a calendar would be: mapping specific events to each calendar. At this point, I confronted the realization that by constantly returning an array, I might need to create a class to manage lists of events.
00:08:21.800 This realization was akin to Java's style, which I found cumbersome as a Rubyist, but I recognized that when you aren't relying on a database, you must actively manage the information you store. Ironically, I started accepting that some Java concepts were valid.
00:08:39.600 I then shifted focus to the event model. Instead of pushing onto an array directly, I assigned each event to the event pool, which functions similarly to that empty array instantiated as needed. The event pool managed the array, tracking instances as they were created.
00:09:06.060 All the existing functionality remained in place while I enhanced methods for current and upcoming events, determining their status and linking to the proper calendar. By restructuring this, I found that class-centric scope management kept the design cleaner.
00:09:36.480 As I moved forward, interactions between methods became clearer. I established methods that dynamically recognized conditions like 'I belong to' calendar ID checks without jeopardizing the encapsulation of the business logic. Everything so far had been about fostering those clean separations.
00:09:58.680 However, it still felt like unnecessary work. The method chaining concept is something any ORM should support out of the box, yet it felt relevant to maintain there. I aimed for encapsulation and single responsibility principles within my structure.
00:10:18.060 Each method needed to stay focused on its own responsibility in the context of domain logic. If an event manages a list of events, that's an unusual approach for a user to deal with and would indicate some fundamental issues that, frankly, should be corrected.
00:10:40.920 I began viewing the event list as a form of in-memory persistence, emphasizing that this method supports dynamic object relationships. The encapsulation every time a decision on ORM is made affects the domain logic, meaning we must remain vigilant about separation.
00:11:04.560 We continue to build this abstraction, ensuring each method is clear and concise. This approach mitigates surprises when ORM changes happen, maintaining clarity across methods based solely on our own definitions.
00:11:22.920 In persistent interactions, my decision-making remained focused and consistent across the board. As we implement persistence, being clear about the significance of the repository pattern from Martin Fowler’s book on Enterprise Application Architecture ensured smooth interactions.
00:11:44.520 An in-memory repository surfaced, granting the functionality to save and manage events without needing a heavy ORM structure. Eventually, I aimed to intersect in-memory persistence with Active Record to seamlessly leverage existing tests while having concise control over the data.
00:12:10.440 In doing so, I learned to manage tests effectively and reliably by creating a foundation built on our class definitions and rules while working collaboratively with Active Record.
00:12:34.080 I understood that by relying on modules, I avoided tight coupling and unpredictable behavior, leading to necessary overrides for methods when incorporating Active Record. By directing focus to our designated API, we guaranteed every required change was coherent with our specifications.
00:12:57.600 With the subclasses able to maintain persistence compatibility while ensuring domain integrity, we established clarity through direct connections and eliminated potential pitfalls of integrating third-party libraries.
00:13:20.520 In conclusion, the approach I adopted led to the creation of extensible adapters that assess efficiently handling various storage solutions. Flexibility grew in parallel, letting us interchange with databases, caching systems, or legacy methods as the need arose.
00:13:42.660 Through all these implementations, what started as an exercise in delaying decisions became a full-fledged in-memory repository that shaped how I thought about object-oriented principles in practice. My inclination to elaborate on structure yielded fruitful insights about our application’s domain model.
00:14:07.380 Remember, when initiating a new project, if you feel tempted to run generation scripts, take a breath and consider delaying those persistence decisions. This small reflection allows us to build better applications over time. Thank you!