Ruby

Object-Oriented Lessons for a Service-Oriented World

Object-Oriented Lessons for a Service-Oriented World

by Chris Kelly

In the video "Object-Oriented Lessons for a Service-Oriented World" presented by Chris Kelly at RailsConf 2013, the speaker discusses how developers transitioning from monolithic Rails applications to service-oriented architectures (SOA) can benefit from applying object-oriented design principles to their software architectures. Kelly highlights the common challenges faced by developers when dealing with legacy applications that become unwieldy and difficult to scale.

Key points discussed include:

  • Evolution of Concepts: The talk is positioned as a conversation, introducing emerging ideas rather than presenting concrete methods.
  • From Monoliths to SOA: Developers often encounter frustrations with monolithic applications and seek ways to transition into scalable architecture without falling into typical SOA traps.
  • Abstraction and Component Communication: Emphasis is placed on using software architecture principles to encapsulate functionality within components and define their interactions clearly, moving away from the complexities often associated with traditional SOA.
  • Object-Oriented Design Principles: Kelly showcases how principles like Single Responsibility, DRY (Don't Repeat Yourself), and Dependency Injection can enhance flexibility in application design.
  • Message Passing and APIs: He argues for a shift from tightly coupled applications to those that communicate via messages, with a focus on defining rich, meaningful APIs that align with object-oriented design principles.
  • Importance of Caching: The necessity of integrating a robust caching strategy from the start to manage network latency and improve response times is stressed.
  • Hypermedia and API Design: Kelly touches on the integration of hypermedia concepts within APIs to facilitate richer interactions and maintain flexibility as systems evolve.
  • Backward Compatibility in API Evolutions: The speaker discusses strategies for evolving APIs without disrupting existing clients, illustrating with New Relic's challenges in managing historical agents.

In conclusion, the video encourages developers to rethink their approach to architecture by embracing object-oriented principles in the context of network-based application software to prevent complexity and improve system adaptability. This paradigm shift promotes the creation of flexible applications that can evolve over time, effectively addressing the needs of both developers and end-users.

00:00:16.480 If you don't know who I am, my name is Chris Kelly. If you thought this was Brian's talk, I think that's next door, so I would recommend that one at some point in time; it's probably a great talk. Brian does a great job. But hopefully you're here to see me, and thank you for coming and giving me some time today. I really appreciate that.
00:00:35.600 On the internet, I go by 'amateurhuman', so you can find me on Twitter and GitHub at amateurhuman. I'm open for anything. I love chatting, and Twitter is usually a great way to get in touch with me. If you want to have a longer conversation, you can always email me afterward. I work for a little company called New Relic; you might have heard we're hiring. I would like to promote that, and I really enjoy working at the company. I'm a Happiness Engineer there, and what that really means is I get to work on stuff that makes people's days better. I'm currently building out a labs group that looks at interesting things in the world that don't have a direct relation to our current products.
00:01:10.080 We experiment a lot and have fun with that. We also have a party tomorrow night, so if you haven't signed up for that, you should come hang out with us and learn a little bit about us. This is really a conversation. These ideas you’re going to hear aren't solid; I haven't said these are how I do things or how I always do things. This is an evolution of some ideas. You’ll hear some emerging concepts, and at some point, we'll even play Buzzword Bingo, so keep that in mind. We’re actually having a conversation here, and I want to continue this conversation afterwards on Twitter.
00:01:46.560 I want to hear your thoughts and know how other people are doing it, because it’s very new. Most recently, I gave this talk for the very first time a week ago in Poland at Railsberry, which was a very exciting conference. Its tagline was 'for curious developers,' so this talk happened to fit really well into that context. I flew halfway around the world to attend that conference, and apparently, it’s a tradition for Railsberry and RailsConf to butt up against each other after a week. So, I have to fly across the world again. Luckily for me, I'm actually speaking on Ruby garbage collection in Scotland, so I'll be flying across the world again after that. After that, I plan to spend a little time in Poland again for my second visit to attend DjangoCon Europe.
00:02:14.640 And don't hate me; I'm just going because I’m halfway across the world. Then I’m going to fly back again. That’s about the next five weeks of my life. I wanted to give you a bit of context about where I’m going and what I do, because a conversation is about building friendships and understanding context. I’ve been bouncing around the world, meeting different people and getting ideas, and that's really where a lot of this talk comes from. It comes from having interacted with many different perspectives.
00:02:54.240 Now, the first web browser I ever used was Lynx. I’m not sure how many of you are familiar with it; it’s a text-based browser that didn’t have images. I was using the Internet before the image tag was even ratified in HTTP. That was a good time. I wasn’t really excited about BASIC or computer programming. What drew me was the Internet. I really came into my own when I could browse online and explore what the world has to offer. That’s where I enjoy investing a lot of my time and thinking, so I’m very much focused on the Internet rather than just being a software developer.
00:03:35.440 This is GIF. It’s an excellent peanut butter. I say 'GIF'; others say 'JIF.' We can disagree on that, but it's animated GIF in my world, not animated JIF. I also love Ruby, and this is important—I call myself a Rubyist, not just a software developer. The tools we use give us a lot of context in our thinking. If I’m a Rubyist, it influences how I think about TDD or the programming language interface. If I were using Python or Java, I would think differently. It's important for you to know that I identify as a Rubyist and care about the language and what it bestows upon me.
00:03:55.520 Thank you, so you're here because you have a monorail. You probably have an application that's a few years old. It's been growing and maturing. You likely weren't thinking about object-oriented design three or four years ago when you started building it. That conversation has really emerged in the last 18 months, and that's been very exciting for us as a community. You might have built your monorail pretty well. It might have some stuff living in the lib directory, so it's isolated and testable. But you're probably feeling that this thing is giant, and you can’t deploy it easily. You’re facing difficulty in scaling and changing it, and you’re here asking, 'How do I get rid of this? How do I fix this problem?' This is probably why you’re all standing here in the back of the room.
00:05:15.760 This isn't really a talk about service-oriented architectures (SOA); this is about that world. SOA has a lot of baggage associated with it, similar to how Ruby does. It brings with it the complexities from the old world of Java, and I don’t want you to think about SOA when considering this. We need to bring some new language into this discussion. If you want to scale, deploy independently, and tackle this situation, what you're talking about is software architecture. That’s what this boils down to. When you're trying to break up a monolithic Rails app, you want to be implementing software architecture.
00:05:59.840 I know what you're thinking; we’re friends, and I can read your mind. You want to do software architecture. You want to split up this monolith into its components, and that's what we're here to discuss. Software architecture is really about partitioning systems. It's about identifying components and how those components communicate together. We need to unpack the baggage surrounding software architecture and think about it in a different way. For software architecture, the biggest aspect to care about is abstraction. We must encapsulate functionality into single components, defining how those components will communicate with each other.
00:06:27.440 I prefer the term 'network-based application software' over SOA, as it characterizes what we are doing better. I don't know if we're building services; I think we are actually building network-based applications—these components that must coordinate to fulfill requests. I am drawing from a phrase by Roy Fielding from his dissertation, which you may or may not have read. So when you think about this, consider it in the context of network-based application software.
00:07:02.240 Let's look at what your application probably resembles right now. You've got this monorail with some kind of caching layer and a database. Inside this monorail, you will find objects that are functioning reasonably well. There may be a god object, but for the most part, you might even be using service objects working through other objects. Most likely, however, your application probably looks a lot messier than that. That’s okay; there’s nothing wrong with having an older codebase. If you look at code from four years ago and don't think to yourself, 'That’s the worst code I've ever written', you might be doing it wrong. It’s merely a reflection of how we grew up in our development practices.
00:07:39.680 But as your friend, I am here to provide some insight. What if we reconsider this image? What if we viewed our applications more as individual objects? Each object could represent a small chunk of application implementation. This is how we often function already—if you use any kind of service integrations, such as from Facebook, Twitter, Zendesk, or a payment processor. You might have legacy applications from an old Java service created by someone long gone from your company, and you are probably already constructing applications that look like this—calling out to these external services. So, instead of attempting to wrap the entire application into this large monorail, let’s split it out in the same manner as we would for our integration points.
00:08:13.680 Now, I understand that your application is unique, like a snowflake. Every application is a special snowflake, and we see how complex they can become. The thing about snowflakes is that they tend to break whenever you modify them. If you’ve spent time studying physics, you would understand how crystalline structures collapse when one component is moved. Snowflakes behave similarly in code. Each change can cause your application to fold in on itself.
00:09:03.840 What we strive for is a system that is malleable and easily modifiable. My applications are never truly done; they will always evolve. So how do we make them flexible? Generally, what happens is that you end up with dependencies. If your crystalline structure is dependent on a specific component and that component is altered, the entire structure may break.
00:09:53.120 Now we have applications that communicate with one another, not just within a monolith, but through messages passed between objects. These objects need to understand each other sufficiently to exchange the right messages. This coupling creates dependencies, and there is a system to manage these dependencies called object-oriented design. Sandy Metz has an amazing book on this topic; if you haven't read it yet, I think you should definitely consider doing so. We want to explore how object-oriented design can be applied to our application layers. Object-oriented design empowers us to create flexible applications.
00:10:25.440 For those of you unfamiliar with the core concepts of objects and object-oriented design, allow me to break it down. An object is made up of two components: state, or data, and behavior. That’s all you need to understand about objects. In an object-oriented system, we have these objects communicating, passing messages back and forth. Some objects might act improperly by directly manipulating another object’s state, which is a behavior we want to avoid. We prefer fully encapsulated objects. The only way to modify state is through behavior, using an external application interface that needs to be called to change another object’s state.
00:11:16.640 This illustrates the core concept of object-oriented design, which you might find appealing. However, you may be wondering how this relates to our earlier conversation about SOA. Well, these ideas aren’t entirely new. They have been considered before in computer science, long before us. The notion of the HTTP object model was introduced 20 years ago—it has since evolved into what we call REST. What we are discussing now is how our objects—seen as applications—can communicate with each other using the principles of REST. REST operates under constraints applied on top of HTTP.
00:12:01.920 As long as you’re still with me, let’s cover some object-oriented design fundamentals to see how our applications can function similarly. There are several aspects of object-oriented design that can help us, such as the principles of DRY (Don't Repeat Yourself) and single responsibility. Not every aspect of object-oriented design will apply to network-based applications, but knowing certain favorable patterns is crucial. For instance, consider the single responsibility principle, which suggests that every component should perform the smallest task possible and nothing more. You may have heard variations of this notion, such as 'do one thing and do it well,' which ties back to the Unix philosophy.
00:12:39.520 Dieter Rams, a significant inspiration behind Apple’s design philosophy, speaks about doing simple designs as ‘doing the simplest thing possible, but not simpler.’ Sandy Metz discusses the concept of single responsibility in terms of cohesion, which I appreciate, because single responsibility doesn’t imply that a component must complete just one narrow task. It means that all the tasks it performs should be coherent and cohesive, having a single sense of responsibility.
00:13:18.640 The DRY principle often gets conflated with avoiding code duplication, but it’s about where knowledge exists—where the authoritative information for an application resides. We want a singular source of truth; thus, two separate objects should not both hold the same pieces of data about an application. Whenever that data needs changing, both objects must be modified, although they should only reference a single source.
00:13:56.080 Next, let's talk about depending on behavior rather than state. Here, behavior is invoked by sending messages between objects. If we have a single-responsibility object that has a well-defined purpose and adheres to the DRY principle, then behavior should exist in only one place—within that singular object. When we depend on behavior, we only have to alter it in one location. This ensures that we operate in a much more flexible manner, allowing us to adapt as needed.
00:14:53.120 Dependency inversion may seem odd, as its name doesn’t accurately reflect its concept. It involves higher-level modules depending on lower-level modules; in essence, we want to minimize direct dependencies. This is about creating abstractions—interfaces for your objects so that they aren’t tightly coupled with each other. That notion often correlates with single responsibility and the DRY principle. Law of Demeter offers another guideline, which relates to how one object should communicate directly with another, without having to go through a third one. This presents some complexity, however, as it boils down to judgment calls in your architecture.
00:15:40.559 Lastly, we have dependency injection, focusing on stateless functionality. We want to pass the necessary information between objects to achieve their requests without skirting around the edges. Many application designs you’ll encounter often succumb to an anti-pattern where multiple services connect to a single database. An example would be saying that tables belong to specific services while attempting to keep them separate. Avoiding dependency injection can lead to quick fixes like eager loading data, which isn’t a sustainable solution.
00:16:14.080 Now, before we wrap up, here’s a public service announcement: cash is king. You cannot take an application into production without a solid caching strategy. Network-based application software incurs substantial overhead from network latency and multiple requests. Failing to implement caching will result in response times that may stretch as long as twelve seconds—definitely not desirable for the end user. When developing applications, especially ones that will communicate frequently across networks, caching must be a first-order feature.
00:17:05.120 Understanding cache control and using ETags correctly is crucial to application performance. Many people utilize them ineffectively, and knowing how to implement caching can be the difference between a responsive application and a frustrating user experience. It's vital to integrate caching from the beginning rather than attempting to implement it later on after deploying thousands of lines of code. I speak from experience; re-implementing caching solutions after the fact is extremely cumbersome.
00:17:52.080 Following our exploration of how to group our classes together, we see that designing an application also involves message passing. In network-based application software, we already have messaging systems through APIs. However, too often, what is presented as an API ends up just being a response in JSON format, and while that is okay, it's essential to dive deeper.
00:18:23.840 Many of us construct APIs hastily without integrating thoughtful interfaces. An interface should not just be an entry point for basic data access; it needs to be a well-considered point of interaction for applications. Interfaces within object-oriented design comprise nouns and verbs. Objects serve as nouns, while messages often represent verbs. For instance, 'user.create' represents a user object interacting with a create message.
00:19:18.240 Unfortunately, our APIs rarely incorporate verbs effectively. Often, they might regurgitate outdated practices from earlier frameworks, limiting available actions to basic CRUD operations like GET, POST, PUT, and DELETE. While HTTP provides verbs that can match actions like reading and creating resources, the systems we build around them often lack diversity in interaction.
00:20:04.560 That brings us to hypermedia, a recent development with considerable potential. Hypermedia adds constraints on REST, a framework that inspires interfaces with richer capabilities. A prime example lies in HAL JSON—a schema for hypermedia that incorporates link relations and state. Such structures provide a more robust framework for interacting with the application, supporting self-describing inputs and outputs.
00:20:53.680 Consider GitHub’s API, which reveals how a well-designed hypermedia structure looks. In their system, elements prefixed with an underscore (e.g., _url) signal hypermedia links, forming a navigable web for consumers. Clients access these links and harness them to interact meaningfully, paving the way for a conversational approach between components. Imagine being able to cancel an order by calling it directly rather than sending a request with various states.
00:21:46.080 I want to discuss how we can design our APIs and applications to embody these principles. There are various things going on in this space. A movement called HATEOAS (Hypermedia as the Engine of Application State) emerges, emphasizing how applications can utilize hypermedia to their advantage. This reflects the concept that hypermedia serves as a conduit for managing application states, emphasizing behavior rather than static definitions.
00:22:38.240 Many mobile application developers face the challenge of maintaining client applications whose APIs they frequently change. The need for a fluid and adaptable API structure becomes increasingly important. As an example, New Relic has faced challenges with historical agents embedded in our clients that cannot simply be removed or updated without significant consequences. APIs must create pathways to navigate any required alterations in functionality while maintaining backward compatibility.
00:23:22.240 Imagine if your client could just look for a cancel link and navigate through changes seamlessly. Thus far, we’ve focused on building media types that are useful, but we must also be cautious. Deploying an API solution may require clients to adapt to multiple media types, leading to further complications concerning library availability and implementation variations.
00:24:06.080 Currently, our HTTP methods need to evolve with our API methods. If a URL exists for functionality like merging pull requests, we need clear indications of which HTTP verb applies. As we explore these object-oriented principles, we should strive for cleaner API interfaces that bypass convoluted versioning and manipulation of objects directly.
00:25:02.239 I think our APIs and API consumers can reflect more of these object-oriented design principles. It creates an opportunity to remove versioning systems, enabling a seamless integration of requests, like directly calling for actions like cancelling an order, regardless of changing underlying URLs.
00:25:40.639 Thank you for your attention. I welcome any questions and discussions, and I hope you can connect with me on Twitter, where I'm known as @amateurhuman.