wroc_love.rb 2015

The Missing System

wroc_love.rb 2015

00:00:15.400 I want to talk a bit about how I try to organize code in my system to plan for growth. A lot of the discussions here focus on how to deal with systems that grow large. Sebastian made a good point earlier: if you want to move to something like microservices, don't do it until you actually know how to structure an application well.
00:00:21.640 These are some of the things I've done to help manage the behavior in my systems. I'm Jim Gay, also known as Saturn Flyer on Twitter, GitHub, and other platforms.
00:00:26.760 I wrote a book called Clean Ruby, and I'm nearly finished with the Ruby DSL Handbook. I’ll be discussing some topics from both of these books today.
00:00:39.640 I'll also talk about a gem called Surrounded, which I developed. Here is the URL for it. I like to visualize my code, and this to me is kind of what a lot of systems look like.
00:00:52.879 There are objects that perform various functions; they share similar attributes or may sometimes be completely different. There are lots of different components in a system, and especially when you encounter a legacy system or onboard a new developer, it can be challenging to communicate what's going on. When they open it up and see something messy and confusing, it can be overwhelming.
00:01:07.680 However, everything begins with one small feature, and at that initial stage, it's straightforward. I want to discuss responsibilities and how we handle them since doing so can significantly impact the software building experience.
00:01:18.799 You could break the software itself, but the experience of managing responsibilities can be fantastic if you're doing it well—easy to work with and change—or it can simply drain your energy.
00:01:36.880 One of the key points I want to emphasize is creating barriers in your code that allow you to make changes. These barriers, in my opinion, can actually provide a lot of freedom.
00:01:49.080 Has anyone read this book or heard of it? Some have, great! This book is by Richard Gabriel, who was influential in the Lisp programming community.
00:01:56.760 If you’ve ever heard the phrase 'worse is better', it originates from him. It conveys the idea that you should create very simple systems and avoid over-architecting your code.
00:02:08.960 You can buy this book; I think it’s out of print, but there's a free PDF available on his site, so I suggest you look for it. I haven’t read the entire thing, but there’s a lot of valuable content.
00:02:24.400 Here's a relevant quote: 'Habitability is the characteristic of source code that enables us to dive in and feel confident about making changes.'
00:02:35.640 This is a bit of sample code I used in a presentation last year at Rubicon. It’s somewhat messy, and if you don’t know what's going on, that’s okay—I’m not going to walk through the entire thing.
00:02:54.560 It grabs values from objects and attempts to create a string representation of an address. It includes elements like address line, city, province, and postal code, and tries to omit certain values if they aren't present.
00:03:06.880 For example, if you don’t have your street address, the output will only show your city and postal code. Looking at this code, it doesn’t feel hospitable; I wouldn’t expect anyone to dive in quickly and see what's happening.
00:03:25.440 In my RubyConf presentation, I demonstrated how I solved this issue. I decided to push all the formatting responsibilities into a template object, allowing the template to manage the formatting of the values.
00:03:41.799 This template takes care of understanding how to format the provided address, and the address simply sends itself to the template method. This person class we discussed earlier receives significant responsibilities pushed down into this object.
00:04:05.760 This approach is easier to manage; if we want to change the format, we know exactly where to make adjustments, without having to search high up in the hierarchy. It essentially operates on the principle of 'tell, don’t ask'—continuously informing objects to perform tasks and pushing responsibilities down rather than querying information externally.
00:04:21.839 This results in clearer responsibilities. Has anyone heard of Christopher Alexander? He’s an architect who significantly influenced many thoughts on software design and architecture.
00:04:41.600 A relevant quote from him is that making buildings for use in a hundred years is quite challenging. Buildings are more permanent than software, yet even he considers how they can evolve over time.
00:04:51.679 Later in the Patterns of Software book, after referencing Christopher Alexander and discussing his ideas, Richard Gabriel suggests that bugs reflect our inability to produce a comprehensive master plan.
00:05:04.800 Much like we can't pinpoint what will be needed in the next two years, or even six months, it’s extremely challenging to create a perfect waterfall plan.
00:05:20.640 Most of the bugs we encounter do not stem from miscalculations but from a lack of understanding of the problem at hand.
00:05:37.600 I'll introduce the tool I use to manage piecemeal growth. Here are the goals I aimed to achieve while working with this code; much of what I wrote stems from research and experiments I conducted while writing Clean Ruby.
00:05:52.600 I aimed to understand DCI—how I can manage responsibilities in my system. The goal is to preserve existing object behavior, particularly concerning the DSLs within.
00:06:06.240 I wanted to localize behavior and apply it only when necessary. I aimed for this to be as non-invasive as possible. For instance, here’s a simple initialization pattern you may come across in your code.
00:06:23.200 You might frequently find yourself doing something akin to this, whether you’re using modules or classes. I often found myself repeating this pattern, which I wanted to simplify.
00:06:39.840 Consequently, I devised a way to keep track of the objects in this class and their roles. I created a map to reference them later.
00:06:53.440 The ZIP here is indicative of the implementation—it looks more simplified when executed. I track the admin and user objects passed in.
00:07:05.840 To streamline my code, I wanted to avoid unnecessary repetition of the same pattern. This is how I handled it while developing the DSL.
00:07:20.560 It’s just a basic class method named 'initialize', which takes several arguments concerning the objects being passed in.
00:07:36.760 It has other methods too, such as setting up reader access and marking certain parts as private.
00:07:50.720 In certain situations, especially when I'm not implementing the same action consistently, I want to retain the established algorithm without repeating the special behavior.
00:08:06.080 I designed my approach to allow modifications without disrupting the existing procedure. Therefore, I created a new 'initialize' method for the point of demonstration.
00:08:21.040 When I call 'super' from this method, it accurately points to the original implementation. My implementation retains whatever structures I previously declared.
00:08:38.600 The functionality rests in that initialized class method. It constructs a module, defines methods on it, and integrates it into the hierarchy.
00:08:55.760 Essentially, this is what is happening here.
00:09:01.480 This is a simplified DSL addressing around 80% of cases. Yet, I enabled the functionality to accommodate additional unique cases.
00:09:20.080 It’s essential to consider coupling, especially in code. We frequently discuss managing dependencies, but cohesion is another important aspect to consider.
00:09:36.720 Cohesion entails how parts of your system relate to one another. When multiple objects share behaviors related to the same functionalities, they are highly cohesive.
00:09:53.360 Conversely, if behaviors are unrelated, cohesion is low. Hence, managing cohesion is critical, and this quote exemplifies how to approach changes in your system.
00:10:10.960 When diving into your code, you don’t want to spend excessive time making sense of it all to find a solution.
00:10:28.000 Consider how much information you need to juggle before addressing the problem you set out to solve.
00:10:43.440 Programs can be complex, with various objects performing different tasks. However, it's difficult to discern which objects relate to each other.
00:10:56.720 To remediate this, I want to build cohesive sets of behaviors amongst the objects within my system.
00:11:10.560 I desire features that feel habitable, allowing me to easily navigate through highly cohesive parts that I can compose and think of in smaller terms.
00:11:20.440 In the Surrounded gem, this mapping occurs automatically. I devised a pattern where I create a module or class, allowing for flexible implementations.
00:11:31.200 Yet, there's no reason not to follow this guideline. You can opt for any implementation you like, whether you place methods in modules or define them in a class.
00:11:43.760 A particular irritation I encountered was the difficulty when changing implementations—especially when many modules or classes were defined.
00:12:00.480 So, I created a DSL that enabled easier selection. By specifying changes within the code, I could adapt to implementations without significant overhead.
00:12:14.960 The system still functions even without the DSL; it handles cases effectively. You maintain the flexibility to create your own classes, provided they map correctly.
00:12:28.000 What does this tool accomplish? The context serves as more than a namespace; it protects access while localizing the behavior.
00:12:36.000 This reputation is vital because I do not want external access to implementation details of certain business processes.
00:12:49.440 I want everything concerning a particular problem managed internally. If we need some implementation, we can utilize that specific object.
00:13:04.080 It's not essential to eliminate all duplication; sometimes, it’s acceptable to repeat behavior if it isn’t identical.
00:13:15.760 Using this approach ensures that business logic remains localized, underscoring the strict limitations imposed by my DSL.
00:13:29.360 Should you venture outside those limits, you can freely share behavior; however, using that roll method guarantees all elements are private.
00:13:38.960 Given everything localized, what should I do with it? My third goal is to apply behavior only as needed, similar to any other class.
00:13:50.800 This class has a range of features, and methods can be defined just like any other object. Here's how I use them: when working with an object derived from the class, all designated behaviors will apply.
00:14:04.880 Thus, each role can wrap or extend its behavior as outlined, and when the actions are performed, the behaviors are unwound in an insure block.
00:14:18.000 This ensures there are no lingering behaviors post-execution, particularly important if an exception arises.
00:14:28.720 I also devised a method to manage behavior wrapping, ensuring the appropriate object behaviors are consistently applied.
00:14:39.920 When I initialize an object like a user activation class, I can assess the triggers within the same context and summon all pertinent methods.
00:14:55.679 If I'm looking for the specifics regarding when these behaviors will apply, I can retrieve all available triggers. I've even used this to build user interfaces.
00:15:09.440 If a user interacts with a portion of my system that has several actions available depending on their role, I can display buttons accordingly.
00:15:27.760 Only the actions relevant to a user are displayed based on predefined behaviors within that context, refining user interactions.
00:15:41.840 Now, if the systems I make are growing through piecemeal growth, how can I effectively manage that evolution? I want the objects passed into my context to communicate with one another.
00:16:00.560 I am treating them as distinct systems, which can be a challenge in any programming language. This can often feel counterintuitive, but with Ruby, it posed specific challenges.
00:16:15.760 Upon starting my research on DCI, I initially dismissed it but later realized its significance through my pain points.
00:16:31.640 In contexts, should I decide to append an object later, it quickly transforms into a complex network, necessitating method signature adjustments, which is cumbersome.
00:16:48.680 Consequently, I revisited the DCI concepts and realized that these objects should act as a unified system that recognizes one another.
00:17:05.040 This technique made it significantly easier to add objects to the system, allowing them to autonomously interact with one another.
00:17:18.160 This module enhances object functionality, granting them access to their current context—here, it's the user activation.
00:17:31.760 Utilizing method missing, objects can examine their context to discover who is present.
00:17:48.000 I want to prevent method signatures from becoming unwieldy by ensuring that objects can access necessary methods without excessive parameters.
00:18:05.160 The design sometimes appears unconventional; predicate methods should ideally return Boolean values. However, the context method allows each object to ask, 'Is this named role within?'
00:18:21.760 If the role isn’t present, it short circuits, continuing on. Should the object exist within the assigned player role, access is granted.
00:18:38.560 I keep track of roles, the objects embodying them, and their behavior constants. I can inquire if a particular name exists and connect it accordingly.
00:18:51.680 An empty block is passed during method missing; this aids in identifying which object is attempting to access other roles.
00:19:07.680 To establish relationships between multiple objects within the same context, these elements can communicate privately, allowing for complex interactions.
00:19:22.720 If I add any behavior to one role, it can command the other roles accordingly.
00:19:36.960 By evaluating the string self from the method binding, I establish which object is sending the message, thus preserving context.
00:19:52.560 Every role retains localized communications, ensuring external objects do not call methods or induce side effects without my logical intervention.
00:20:07.920 Allowing only objects contained within the context to access other objects defines how implementation details are managed.
00:20:22.560 It's necessary to reflect on how accessible the implementation details should be, ensuring private constants and methods are preserved, contrary to popular Ruby practices.
00:20:37.760 Many Rubyists tend to publicly declare methods, but I encourage trying the opposite while recognizing how effective it can be.
00:20:50.880 Richard Gabriel emphasizes in his book that piecemeal growth is a reality that we often overlook. We don't start by constructing vast, intricate systems.
00:21:07.040 While we might understand the complexity of certain domains, they tend to evolve over time, highlighting the learning process inherent in software development.
00:21:21.760 Utilizing this tool allows for gradual growth; what begins with two interacting objects can expand naturally as additional elements emerge.
00:21:38.800 Should you decide to integrate this approach? While this may not perfectly align with everyone’s needs, especially if components seem overly complex, it grew on me.
00:21:55.760 Initially, I hesitated to implement certain features, but over time, I recognized their necessity, reflecting how I cultivated both my systems and the Surrounded project.
00:22:12.160 Thank you.
00:22:18.560 Thanks, Jim. Questions, anyone?
00:22:34.640 Do you feel like it's somewhat similar to the concept of bounded context? Before you define bounded context, you could apply it and later extract it as a microservice?
00:22:47.440 Yes, while I can’t claim extensive knowledge about DDD, listening to the panel made me realize that people's view of DCI may miss the detail that a specific role in one context doesn't need to share code with roles in another.
00:22:58.360 Exactly!
00:23:05.840 How easy is it to write tests using this gem? What does it take to test the implementations?
00:23:18.080 The testing process chiefly concerns how you compose objects. Your goal is to facilitate task allocation within a system.
00:23:32.960 Thus, while testing, you want the external world to perceive a change in state after object interaction, employing a black box approach.
00:23:47.760 Are the contexts and use cases composable? If they aren't, how do you avoid redundancy during implementation?
00:24:01.680 I often find myself needing to reuse objects in other contexts, and this model accommodates that without forcing continued use of the same context.
00:24:14.000 One of the remarkable features here is the ability to compactly map behavior, enabling fast construction of contexts.
00:24:27.280 The ease of creating multiple contexts permits me to experiment with distinct implementations quickly, free from the burden of permanently altering larger structures.
00:24:42.160 You might discover many advantages to building these rapidly since your approach remains compartmentalized, ultimately simplifying conceptualization.
00:24:56.720 What is 'respond_to_missing'? Is it a Ruby feature or part of your code?
00:25:09.720 That's actually part of Ruby's functionality. If you define 'method_missing', you must also set up 'respond_to_missing' to signal if the object responds to specific messages.
00:25:20.640 What is your new book about? Is it focused on using DSL for specific purposes?
00:25:33.560 I’m writing the Ruby DSL Handbook, which stems from my client work focused on QA automation and making declarative representations for web pages.
00:25:48.240 While teaching developers about the structure of designs, I aimed to maintain flexibility, ensuring that dynamically-defined methods remain localized.
00:26:03.040 It’s a short book sharing simple ideas to reduce complexities during the development phase.
00:26:11.680 Any more questions?
00:26:22.600 Thank you very much, Jim.