00:00:04.259
Thank you. So, did anybody hang out with Nathan last night?
00:00:09.599
Yeah, so what’s the bet? Does anybody have a bet on what time he's going to wake up today based on the state he was in when he left?
00:00:14.700
My bet is that he's in Berlin right now, as Andre said.
00:00:22.320
I'm Scott Bellware. I'm the co-founder of Eventide, which is a toolkit for evented autonomous components.
00:00:29.220
Like I said yesterday, we're finally able to take the words 'services' and 'microservices' out of the tool name now that those terms are no longer popular.
00:00:35.280
Eventide is a toolkit for building evented systems using Pub/Sub autonomous components and event sourcing.
00:00:40.860
We just came back from our first summit, a mini-conference held in British Columbia, Canada, where several of us gathered to plan the next major version of Eventide. We also held a contributor boot camp, inviting anyone interested in becoming an open-source developer to participate and receive training to get started.
00:01:05.640
We work on a product called Neuron, which is a legal process automation toolkit for one of the leading venture capital law firms in the world. This firm has locations in San Francisco, London, Sydney, Hong Kong, and other major cities. All the core logic is built on Eventide, while the front end is developed using Rails.
00:01:19.380
So, what is Eventide? It's an open-source project, and our community plays a significant role in everything we do.
00:01:24.840
It's a mindset, a methodology, and also an educational platform. When we talk about the Eventide framework, we're not just referring to software tools; we consider that a toolkit.
00:01:30.300
The framework informs our thinking, and a major aspect of this is not only building software but also teaching.
00:01:36.480
One thing we're very proud of is winning the Ruby Award in 2019 for social impact, which acknowledged our engagement with the community and our commitment to education.
00:01:42.180
Mostly, when we discuss software development, we're focused on object-oriented development. We also cover methodology, management, UI/UX, testing, and other areas. However, at its core, we emphasize object-oriented programming.
00:01:52.560
There's an old saying that object orientation allows for advantages but doesn't provide advantages. This will be an important point to keep in mind as we go through today's presentation, which will center on design and the fundamentals of design.
00:02:00.180
Let's talk a bit about people, specifically programmers. This shape should hopefully look familiar; it's a normal distribution curve, or a bell curve.
00:02:06.600
A bell curve can be divided into standard deviations. One common way to analyze a group of people is to look at six standard deviations, or six sigmas. Observations show that typically about 70 percent of the population falls within Sigma 3 and Sigma 4, which represent the average. If you take average height, weight, income, or any other measurable attribute, you will usually find that the middle part of the bell curve contains the largest number of people.
00:02:22.920
This same curve is used to describe adoption. Roger's adoption curve explores how any technology develops within human society, from its invention to its later stages. The numbers roughly align, with about 70 percent of the population residing in the middle.
00:02:36.240
In both cases, we observe averages or common characteristics in the middle while reserving the edges for exceptional cases, which typically are oriented from low to high on a left-to-right axis. In the adoption curve, skills or capabilities can be represented similarly, appearing low on the left and high on the right, with most people clustering in the middle.
00:02:51.480
Returning to the adoption curve, an interesting phenomenon is the chasm. This has been a topic of significant discussion in recent decades. The term 'crossing the chasm' often relates to the difficulties of getting technology adopted by the majority outside early adopters.
00:03:07.560
Innovators are the inventors; they easily adopt their technology, and early adopters follow suit. However, moving to the early majority or the majority can present challenges. The chasm appears to consume knowledge and understanding.
00:03:19.440
People in the early majority are separate from the early adopters—different social groups that don't communicate. This lack of understanding makes it challenging for the majority to integrate knowledge from early adopters.
00:03:31.740
Instead, the majority often follow even more exceptional individuals—the inventors and innovators. This gap in communication creates a huge challenge.
00:03:43.860
This leads to a somewhat shocking assertion: Active Record objects are simply data structures that are built in an object-oriented language. While any language will require data structures, that doesn't inherently make them object-oriented. Each programming paradigm influences the design of data structures.
00:03:57.300
For those learning software development through Rails, it's possible you haven't grasped the principles of object orientation yet. This contributes to the difficulties many Rails systems and applications face.
00:04:11.520
Object orientation allows for advantages, but it doesn't inherently provide them. You might question if this matters if your company is progressing and your team is functioning well. However, the underlying issue is that development is supposed to be easier than it often is in practice.
00:04:27.900
Let’s discuss fundamentals that will help clarify why challenges arise and what strategies you can implement in the future. First, consider afferent and efferent coupling, as well as generalization and specialization.
00:04:39.900
These concepts address how abstract or concrete a unit of design may be. We will delve into these topics deeper as we proceed, but for now, I'll present the following principles.
00:04:51.240
In software development, understanding single responsibility is crucial. A single unit of software should focus on one task or area rather than many. This connects to the principle of 'tell, don't ask,' a manifestation of encapsulation that helps prevent software entanglement.
00:05:06.240
Next, let's review the coupling dimension. Afferent coupling refers to calls into an object, while efferent coupling refers to calls out of an object.
00:05:12.880
For example, an object with high afferent coupling means many other objects rely on this object, while an object with low afferent coupling experiences less reliance.
00:05:20.640
To illustrate, if we have two scenarios, which one is easier to change? Presumably, the one with lower afferent coupling would be less troublesome to modify.
00:05:32.040
When you change a highly dependent unit of software, you must also investigate and potentially validate all the software pieces entangled with it. Thus, as we review the coupling dimension as a continuum, we will evaluate how afferent and efferent coupling impact our software.
00:05:49.560
Moreover, let's think of our conceptual dimension as a vertical continuum. I'll provide examples of afferent and efferent components. A Rails model, for instance, is quite central to an application, placing it in the afferent category.
00:06:07.740
An example of efferent could be a controller—it typically only interacts with other objects while remaining independent. No other code invokes it directly except through the front controller or web framework.
00:06:16.680
By positioning these components, we arrive at four quadrants. The upper section contains components expected to change rarely, while those below are anticipated to change frequently.
00:06:31.860
This creates a happy quadrant of afferent and generalized objects, like a system object in Java or C#. It has high afferent coupling since everything tends to derive from it—these objects are generic and don’t serve specific purposes. Conversely, the other happy quadrant consists of efferent and specialized elements, like a controller class.
00:06:49.440
These controller classes orchestrate other objects, initiating outbound calls specific to their operations.
00:07:06.240
We find other quadrants less beneficial as the first describes a scenario where a general class possesses all methods from other classes—this becomes an absurdity. Such a model class would result in an inversion of abstraction.
00:07:20.340
Similarly, the last quadrant features an efferent component with many inbound calls but also specific functionality. This configuration can lead to absurd outcomes.
00:07:39.900
Now, considering the 'fat model, skinny controller' ideology in Rails software design, which quadrant does this fall into? It is indeed one of the absurd categories.
00:07:52.920
Our experience shows that 'fat model, skinny controller' leads to fundamental mistakes in software design. This practice places specificity in the model objects that should only be present in controller classes.
00:08:07.140
If your model object contains code only utilized by a single controller, it indicatively suggests crossed responsibilities, as it blurs the line between model and controller roles.
00:08:16.740
Additionally, this configuration undermines testing as Rails controllers are challenging to test properly. If test-driven development (TDD) had been applied appropriately from the start, this issue could've been easily avoided.
00:08:30.840
Retrofitting software to adhere to a mistaken paradigm feels like a last-ditch effort to validate a non-ideal design. To clarify this further, it's helpful to remember a handy acronym representing the desired design quadrant and process.
00:08:48.600
I have a blog write-up discussing the 'distance from the main sequence' metric. The term originally coined was somewhat abstract but represents a fundamental design principle that everything should serve to restrict dependencies and reduce the risks associated with changes.
00:09:07.740
Essentially, a useful object performs a specific task and should change only in rare circumstances. The meaning behind 'tell, don't ask' conveys the importance of encapsulating responsibilities correctly.
00:09:22.740
To recap: our focus is on reducing afferent coupling as a way to maintain manageable change within our software architecture.
00:09:38.460
What constitutes a useful object? This doctrine shapes how we approach all of our software development and object-oriented systems here at Eventide. All our tools and customer projects follow this paradigm. Adhering to this methodology simplifies nearly 40 years of object-oriented design.
00:09:56.520
A useful object must have all its dependencies instantiated upon creation. None should pose a risk of being nil, otherwise, it results in runtime errors.
00:10:09.600
This is a fundamental design issue because nil reference errors render components unusable—thus, leading to frustration.
00:10:23.880
Moreover, we further differentiate an object's initializer from logic. Initializers should only accept and record primitive values they receive.
00:10:39.840
In doing this, we isolate the primary creation concern from other potential complications.
00:10:46.380
Additionally, we ensure the object doesn’t require an excessive structure like an inversion of control container just to function properly.
00:10:59.520
As a result of these strategies, we diminish the need for test doubles or mock objects and instead depend more on telemetry, embracing a cleaner design.
00:11:10.200
The objective is that our test code remains sacred, avoiding unnecessary complication, which leads to enhanced clarity.
00:11:22.680
Now, let's preview a code snippet showcasing a simplistic test case. This case will sign up a user through an HTTP client, albeit using a custom protocol instead of JSON.
00:11:36.000
The 'sign up' class should feel intuitive, using dependency injection to facilitate user registration.
00:11:48.600
However, a complication appears in the test setup—the infrastructure setups obscure the intent of the test, making it less scannable and readable, which hinders the overall clarity.
00:12:04.380
When test code is cluttered with irrelevant structures, it disrupts the flow of knowledge retrieval from that code, resulting in reduced efficiency.
00:12:22.680
In this scenario, passing nil values during initialization leads to predictable errors. Instead, our design objectives dictate that these objects need to be usable from the moment they are instantiated.
00:12:40.680
Additionally, we'd prefer not needing to consider the HTTP client during a test, properly abstracting it away from the code.
00:12:56.280
Using overrideable methods or optional arguments can even warrant the warning that our design may not be optimal.
00:13:12.840
Rather, we seek the initializer to express conceptually essential knowledge, not just dependency mechanics. That’s our end goal.
00:13:25.080
In that vein, the 'sign up' class should cleanly receive user data, streamlining direct comprehension without convoluted reasoning. By converting the dependency into an attribute, we enhance simplicity.
00:13:40.560
However, if that attribute defaults to nil, it can induce nil reference errors. We must not only address this but also ensure that a strict adherence to healthy design practices prevails.
00:14:00.840
Using null coalescing patterns allows for a graceful handling of potentially nil instances, enabling seamless usability.
00:14:17.380
Yet, we impose a creative constraint; we need to maintain our ability to manage and control our dependencies effectively.
00:14:32.880
Introducing a specific library, 'mimic', can officially establish null objects—allowing an instruction to be implemented while ensuring that no real HTTP calls are necessary during tests.
00:14:46.640
Through defining parameters properly, we avoid the risk of triggering live services elsewhere. Instead, we can attach diagnostic purposes without compromising our design.
00:15:00.000
The diagnostic aspect of our design allows gathering telemetry and analyzing interactions effectively, ensuring better transparency.
00:15:21.100
Now, I will refine the design further by implementing a dependency macro library that creates easy-to-read imbued dependencies.
00:15:37.500
This library allows leveraging specialized methods through defined interfaces—upholding a certain level of abstraction while avoiding excessive complexity.
00:15:55.140
Utilizing these constructs allows our classes to maintain clarity across both convenience and mechanics, leading to a solid implementation.
00:16:10.700
When invoking the initializer for dependencies, we must acknowledge how they interact within the context of our application.
00:16:28.540
To wrap up, the classes we design seek to provide both clarity and control—allowing them to operate as intended without interference from external structures.
00:16:44.640
We have painstakingly adopted an approach that promises high degrees of maintainability without convoluting the core logic with dependencies.
00:17:02.320
Moving further, we operationally separate tests from core logic, encapsulating them distinctly and assigning designated structures to each concern.
00:17:19.120
With that, let’s open the floor to questions, comments, or critiques. If any objections arise, please feel free to present them.
00:17:39.960
Yes, I have a question. Yesterday you mentioned that one can gauge the quality of a test based on its dependency setup. Could you elaborate on how this relates to the design of our sign up process?
00:17:58.920
In our situation, the HTTP client is essential for sign up, yet we rely primarily on its interface rather than its implementation.
00:18:10.680
Yes, the nature of this test examines interactions. The purpose is to confirm proper communication with the HTTP client during the sign-up process.
00:18:27.120
In cases where the HTTP client returns values, we would implement specialized methods to facilitate this requirement.
00:18:44.040
Essentially, we would encapsulate expectations for return values while providing checks for interaction quality.
00:18:56.640
So essentially, we navigate through testing practices with caution by emphasizing direct interactions to gauge functionality.
00:19:09.840
Correct.
00:19:20.960
This leads to a discussion on whether the dependencies and design structures we use truly influence the clarity versus complexity in our implementations.
00:19:31.640
While employing dependency injection containers can offer convenience, we must emphasize the autonomy of our classes to configure their dependencies without reliance on external frameworks.
00:19:47.780
Thus, the core principle ensures minimal external entanglements while enhancing the maintainability of our designs.
00:20:00.520
Thank you, everyone! It’s been great sharing these insights with you all. If there are any further queries, feel free to reach out!
00:20:11.240
So, it looks like we’re out of time. Thank you for your attention!