00:00:04.080
Hello, everybody. Sorry, let me fix my slides. I wrote these in the United States, so you can read them here.
00:00:10.240
I was thinking about what I wanted to talk about, and I recently did a bunch of refactoring on some projects.
00:00:18.680
This experience gave me an idea about how to take an idea and implement it in code.
00:00:24.160
There are many times when the challenges are hidden below the surface.
00:00:29.439
I wanted to walk you through a refactoring I did in the past few months.
00:00:37.640
First off, let’s talk about what intuitive code is.
00:00:42.800
When you use something like Rails and you wonder if a method exists, you just try it.
00:00:49.120
It’s a wonderful feeling when you discover that someone else has already thought of that method and implemented it.
00:00:56.719
Ruby is a language that naturally reads kind of like English.
00:01:02.680
We even have question marks at the ends of methods, which is quite awesome.
00:01:08.280
However, this is not enough to make code feel intuitive.
00:01:13.600
Naming things is difficult; architecting them is also a challenge.
00:01:19.840
Overall, it can be quite hard.
00:01:25.240
Let's look at an example: notifications.
00:01:30.600
If I asked you to implement notifications, your instinct might be that it's easy.
00:01:35.720
After all, you might just think you can send an SMS or a message to Slack, right?
00:01:42.560
However, it’s really not that simple.
00:01:49.079
We've implemented notifications in all of our applications differently every time.
00:01:56.399
In the end, they all end up being a bit annoying in one way or another.
00:02:02.680
So I thought, why not try to map this out?
00:02:08.840
I saw a tweet from about five or six years ago.
00:02:14.519
It discussed how Slack decides to send a notification.
00:02:23.000
There are many factors to consider: user preferences, Do Not Disturb mode, priority issues, and so on.
00:02:30.440
There are many decisions that come into play when considering how to deliver a notification.
00:02:36.519
For instance, they might send you a push notification.
00:02:43.159
If you read it, they know they don't need to send a notification later via email.
00:02:49.560
However, if you don't read it, they may escalate it to your phone, followed by an email.
00:02:55.640
This illustrates the complexity of the logic involved.
00:03:01.959
Some examples of notifications include a notification bar that pulls records from the database.
00:03:08.720
These records might also need to be broadcast in real-time via WebSocket.
00:03:15.159
If you are on the page and someone comments on something, we want to show that immediately.
00:03:20.840
You might want to send messages to Discord, Slack, or Teams.
00:03:26.159
This could be done via webhooks or API calls.
00:03:31.280
Of course, we also have SMS through services like Twilio or Apple's Push Notification Service.
00:03:38.599
And let's not forget about email.
00:03:43.799
So I was thinking: surely we can figure out how to do this in Ruby, making it feel intuitive.
00:03:50.239
I wanted to create a new project for this process.
00:03:55.920
I started building a gem called Noti, seeking inspiration for its design.
00:04:01.239
I wanted it to feel like Rails because it would be used in Rails applications.
00:04:07.599
I looked at Action Mailer and Active Job to model the behavior.
00:04:13.519
We can define the logic in a class and provide context via a block.
00:04:19.160
We can specify what to send, such as the new comment email, and prompt to deliver it in the background.
00:04:24.560
Notifications generally operate in the background, making it a suitable model.
00:04:31.320
My vision included a class that would allow the definition of multiple delivery methods.
00:04:36.479
We could specify delivery options such as email, Action Cable, and the database.
00:04:42.520
We might want to send an email only if the user has opted to receive emails.
00:04:49.759
Additionally, we could delay the emailing for five minutes, checking if the notification was read.
00:04:56.479
We wanted a flexible interface that establishes all of these rules.
00:05:01.639
Making a complex decision tree similar to Slack's, if needed, would be possible.
00:05:07.440
This would allow us to implement helper methods, ensuring clarity for users.
00:05:13.600
So, how do we implement something like this?
00:05:19.120
We can start by creating a parent class from which our notification classes can inherit.
00:05:25.400
We merely need to collect the delivery method details, which can be stored in an array.
00:05:32.680
This initial step is quite simple, perhaps even too easy.
00:05:40.120
Now, if we add from the previous example, say, iOS push notifications,
00:05:46.840
we end up needing quite a bit of context to send a notification.
00:05:52.919
We require certificates, key IDs, team IDs, connection pool size, and other elements.
00:06:00.440
This makes the class harder to understand since the iOS specifics are not well contained.
00:06:08.120
This could be similar if you've set up associations or custom validations in Active Record.
00:06:14.440
You can specify a symbol that calls a method of the same name.
00:06:21.039
But Rails configuration provides a config block that gives you a config object.
00:06:27.919
Here, you can set options and allow passage of a block for better organization.
00:06:38.560
Thus, the prior code can be condensed and reorganized.
00:06:44.480
This enables hiding iOS details when not needed.
00:06:50.440
We need a high-level understanding of what this notification is destined to do.
00:06:56.440
We can then update our code appropriately.
00:07:02.440
If a block is provided, we’ll return the object for adjustments.
00:07:09.680
We save this as ordered options instead of raw hash.
00:07:16.440
Now, ordered options serve as our basic context.
00:07:23.599
To build our deliveries for comments, we can animate our previously defined deliveries.
00:07:31.039
The delivery method collects recipients for notifications.
00:07:38.920
Each recipient allows us to queue up jobs, sending emails and updating the database.
00:07:46.079
However, once we go through each recipient, we must queue up those jobs.
00:07:51.280
To do this, we need to find jobs with the right symbol, connecting them with relevant classes.
00:07:57.840
So we need to lookup in the namespace to convert symbols into corresponding classes.
00:08:03.400
We'll use constant tools to fetch the Ruby object and capture it.
00:08:11.120
Now, we can set the method variable for these Active Jobs.
00:08:16.960
Here we can introduce delays, allowing Sidekiq to manage these effectively.
00:08:25.000
This part of the code begins to grow complex and messy.
00:08:31.960
At this stage, we should consider extracting potentially confusing classes.
00:08:38.000
We want to avoid code that’s only piecing together configurations.
00:08:45.280
If we analyze the job functions, we can detect a degree of over-extraction.
00:08:53.080
The current storage mechanism is simply a hash of hashes.
00:08:58.080
This setup does not afford any internal functionality.
00:09:06.680
Thus, we should posit a delivery-by-object to consolidate required functions.
00:09:10.680
Ultimately, this object needs to know how to set options.
00:09:18.480
The job needs specifications as such, knowing the sender and method.
00:09:24.160
We can delineate these from each other efficiently.
00:09:30.720
Now we have simplistic objects that simplify our process.
00:09:39.560
This shift enables us to specify how delivery works as we see fit.
00:09:46.760
We no longer need to externally understand the technical architecture.
00:09:54.720
This closure showcases the concept of ordered options and their benefits.
00:10:03.520
For Rails application development, these parameters allow expansion.
00:10:10.080
While a hash is fine for developers, it lacks friendly interaction in Ruby.
00:10:15.400
Thus, we can build within code maintainability and effortless communication.
00:10:22.399
For delivering messages, we'll gather the email delivery constants.
00:10:30.679
Upon verification, we’ll validate the five-minute wait option.
00:10:38.320
If you examine Rails source code, it tends to be surprisingly simple.
00:10:42.400
All it does is instantiate a minor object that takes your job and stores the options.
00:10:52.200
This can be accomplished in relatively few lines.
00:10:59.480
It creates a surface layer that remains hidden in the process.
00:11:05.680
This approach is advantageous to not require intrusive modifications.
00:11:11.480
We can implement the external methods without altering Active Job's core.
00:11:18.080
You might overlook tiny concepts that lead to messy implementations.
00:11:24.760
You can additionally structure the database and deliveries more efficiently.
00:11:30.960
Certainly, database delivery methods turned out to be more unique than anticipated.
00:11:38.560
These must lead priority as we manage broadcasts and emails.
00:11:44.320
To send notifications, we need to confirm data in our database.
00:11:50.639
Tracking advance requires to ensure clicking mechanisms function.
00:11:56.399
We will begin routing these priorities logically.
00:12:00.520
Upon execution, we can check each database delivery and set its reliance.
00:12:06.599
With each step, we assess interplay between collected parameters.
00:12:12.480
Now that we've segmented deliveries, you may think it’s manageable.
00:12:18.880
We needed to derive functionality, not solely interpretations.
00:12:26.839
As a note, we accomplish database storage effectively and simply.
00:12:34.919
The notification models can now flourish in derivatives of objective designs.
00:12:44.799
Conversions allow for a dynamic approach regarding filtering oddities.
00:12:51.959
In doing so, we're capable of deriving contextual attributes in our class structures.
00:12:58.920
This allows us to guide pathing expectations through clearer structures.
00:13:07.440
Efficiency surges arise while managing notifications in active record.
00:13:14.920
This results in orderly procedures grounded in well-structured code.
00:13:22.400
Redirecting complicated notions enhances the capacity to maintain effective design patterns.
00:13:29.920
The dagger of duplicated parameters continues at unwanted moments.
00:13:36.320
Effectively managing these ties prevents a convoluted mess from future rewrites.
00:13:44.200
Next, we build variations on event processing, mapping out visible responses.
00:13:52.960
Different events trigger notifiers to formulate external notifications.
00:14:00.920
As we understand what events guide, those in return open further pathways.
00:14:09.199
Tracking all communications can provoke rapid inquiry into redundant areas.
00:14:16.600
Understanding boundaries is integral; clarity signifies improvement.
00:14:22.720
Separating notification records simplifies confusion within future implementation.
00:14:30.639
We should build towards more manageable scenarios while optimizing processes.
00:14:39.200
Ultimately, our goal is clear communication between events and notifiers.
00:14:46.720
Soon, renaming options will clarify purpose and process further.
00:14:53.440
Redirecting systems opens up virtual styles for manual implementation.
00:15:01.320
Dynamic job methods deliver affordances enabled through databases.
00:15:05.520
Efficiency maintains integrity guiding methods of response.
00:15:12.240
Foundational organization becomes necessary.
00:15:19.919
We seek out a holistic perspective involving redefined roles.
00:15:26.639
To this end, notifiers hold everything in sync and cohesive.
00:15:32.000
Thus, they challenge conventional design methods by achieving clarity.
00:15:41.040
This union of structures allows you to manage internal identifiers.
00:15:48.360
Code itself becomes extensible without entail vulnerable complexities.
00:15:56.760
Enhanced future-proof notifications aim for lower friction communication paths.
00:16:04.640
Ongoing changes serve to clarify user feedback in responsive environments.
00:16:12.640
Upon reshaping designs, larger frameworks will naturally support granularity.
00:16:20.000
The next generation of Rails will solidify and build upon the foundation laid here.
00:16:28.000
This will culminate in a comprehensive understanding of notification systems.
00:16:36.000
As we move forward with implementations, the insights gathered here can guide the future.
00:16:44.000
Thank you, and I hope you learned something valuable today!