00:00:10.240
Thank you, everybody, for coming to this talk. I know there are at least ten other talks happening right now.
00:00:16.160
All of them seem really cool, so I appreciate you coming to my talk. I'm going to get into Sorbet, which is a gradual static typing library for Ruby.
00:00:29.359
You've probably heard of it, and specifically, I'm going to talk about how we use it at Grailed to gain more confidence in the code that we're writing and shipping.
00:00:37.360
First, a little bit about me: my name is Jose Rosello, I live in Brooklyn, and I am a staff engineer at Grailed.
00:00:49.039
Over the past couple of years, I've worked on infrastructure and security, and the last thing I'm doing is payments. I've worn a few different hats during my time here.
00:00:55.760
This is an overview of the talk. I'll give you some context about where Grailed was and what finally motivated us to adopt static typing.
00:01:07.840
I’ll provide a quick crash course on Sorbet and walk you through how we rolled it out at Grailed, as well as some of the challenges we faced. Then, I’ll leave you with some food for thought.
00:01:21.200
Grailed is a peer-to-peer marketplace focused on style and fashion, primarily luxury goods for men. Think of it as eBay, but if eBay were cool.
00:01:32.240
We currently have over seven million users, and we use Rails almost exclusively to power our API. It's a large Rails monolith that has grown from 30,000 lines of code in 2017 to around 150,000 lines today.
00:01:45.840
Our developers are fairly familiar with static typing and gradual typing through our front end; we use TypeScript there for our Next.js code. The last line I have here is a bit of an exaggeration, but it truly was motivated by an engineer who was tired of shipping null pointer exceptions and subtle type issues.
00:02:10.800
He figured this would be a good exploration to see if it could solve those problems. So, why static typing in Ruby, to begin with?
00:02:18.239
For me, the biggest concern was feeling confident around refactoring and knowing the changes you make are safe. Ruby is dynamically typed, so if you want to refactor a method, you might get lucky and find it easily, but what if the method name is common?
00:02:43.360
Then you need to be really good at grepping and inspecting call sites. You might think unit tests will catch any changes, but they are subject to human error and the quality of the tests themselves.
00:03:01.599
We love our unit tests and have a lot of good coverage, but you can still miss things, especially with logic issues not covered by new tests.
00:03:14.560
Rails and Active Record are highly dynamic, making it easy to make assumptions about data presence. This can lead to null pointers sneaking in and breaking your code. There are numerous instances of type issues in the form of pull request titles that we've fixed over the years.
00:03:43.440
The other side of the coin is legibility and documentation. Using YARD docs has its advantages as they provide a standardized way to document types, but comments are just that—comments. Developers are not generally trained to assure comment accuracy. When code is refactored, it's easy to forget to update comments, leading to stale or misleading documentation.
00:04:41.199
Working at Grailed, which is now about 150,000 lines of Ruby code, you're often faced with unfamiliar code. Seeing types that are enforced by the type checker provides a clearer understanding of what the code does.
00:05:22.320
Before I dive into Sorbet, I acknowledge that this is a Ruby conference, and discussions on typing can evoke strong feelings. Some people don't see the value in type annotations.
00:05:35.840
I looked for scientific research supporting types, but the results were fairly split. It's unclear if the trade-offs of adding static types are worthwhile, and much of the research focuses on toy codebases in older languages without modern type system guarantees.
00:06:06.960
That being said, I'll get into Sorbet. If you dislike strong types, please don’t heckle me; just enjoy the presentation.
00:06:32.000
Sorbet is a gradual typing library. This means you can choose your own level of enforcement, and regardless of your codebase size, you can get started with Sorbet almost immediately by implementing minimal enforcement.
00:06:49.679
Sorbet does this through the concept of sigils—magic comments declared at the top of files. Enforcement happens at the file level. If you don't have a sigil, Sorbet treats your file as if it has the default setting, checking only for syntax errors and missing constants.
00:07:19.679
With 'true' sigil, Sorbet checks that invoked methods exist and verifies the correct number of arguments are passed. Any statically typed signatures in your file will also be validated.
00:07:43.440
The 'strict' sigil means every method and constant must have static types declared. Lastly, 'strong' is the ultimate level of enforcement, requiring that every method call invokes typed code.
00:08:06.879
We aim for 'true' on existing files, and for any new code, we strive to make it 'strict.'
00:08:37.200
Here's an example of a Sorbet signature. If this is your first time seeing one, you might find it looks a bit hideous compared to other programming languages. The key takeaway is that this is still valid Ruby code.
00:09:18.560
Unlike languages requiring transpilation, Sorbet allows you to ship valid Ruby directly to production. This functionality also enables Sorbet to perform runtime checks.
00:09:27.760
I believe this is really crucial, especially for gradual typing systems. Since some code will be untyped while other code is typed, the untyped code may invoke methods that are typed.
00:10:01.360
At runtime, if Sorbet detects that the wrong types are being passed to a typed method, it raises an exception. However, at Grailed, we treat these alerts differently.
00:10:32.640
Instead of treating these as critical failures, we alert ourselves through a Slack channel. When we add types to old code and the wrong inputs are detected, we receive a Slack notification, allowing us to correct the issue without punishing ourselves in production.
00:11:06.720
This process has been beneficial; we've learned a lot about our codebase, added types in the process, and we’ve never broken production due to this.
00:11:37.200
Although we've primarily focused on typing our code, what about our dependencies? Sorbet's answer to this is the Ruby Interface file (RBI).
00:12:11.040
An RBI essentially serves as a skeleton of code, declaring classes, methods, and constants without actual body implementations. Sorbet inspects your gem code and generates these declarations.
00:12:43.040
These skeletons have advantages; firstly, they allow Sorbet to maintain speed without having to inspect entire gem bodies, which could slow down performance. Secondly, if type inconsistencies arise in your gems, you may need to manually edit the incompatible code.
00:13:18.159
Let me walk you through an example of what an RBI looks like when generated by Sorbet after inspecting a gem. It outlines the general structure of a class but does not provide information about types.
00:13:45.600
In another example, we have a designer, which is an Active Record model. This one was generated by Sorbet Rails, which inspects Active Record models and generates signatures based on the database column types.
00:14:02.400
Because Active Record understands the types of database columns, it can generate more informative signatures. This demonstrates Sorbet's capability of understanding more complex types.
00:14:35.360
Next, I'll cover some of Sorbet’s greatest features. One is T.nilable, which functions like an option type found in other programming languages, ensuring that you can't call a method that might not belong to your object.
00:15:13.920
Another feature is exhaustiveness checking. If you create an enum but fail to account for all possible values in the calling code, Sorbet will alert you of unhandled cases.
00:15:32.640
The type checker will let you know if any potential values are overlooked, which is particularly useful for errors in payments, where such considerations are crucial.
00:16:14.560
T.struct is another utility we frequently utilize. It allows you to declare fields and types for a data structure while optionally enforcing immutability, making it clearer to understand what kind of data is being handled.
00:16:52.480
An issue with Ruby is its implicit returns, which can lead to unintended consequences. The void return type prevents this and guarantees that a method behaves correctly without relying on output.
00:17:34.560
Now, let me walk you through our rollout process. The initial steps are fairly straightforward, and Sorbet comes with utilities that help generate RBIs for your gems, allowing you to start running checks almost immediately.
00:18:05.920
We encountered issues with dynamic imports that led to undefined constants identified right from the start. This was particularly challenging due to Rails’ highly dynamic nature.
00:18:37.679
We leveraged Sorbet Rails, which assists in addressing many Rails-related challenges by providing RBIs for all dynamic methods generated by Rails, particularly for Active Record models.
00:19:06.760
We also utilized Tapioca, a tool from Shopify that provides similar functionality but is friendlier and offers more customization options. For the Ruby linter, we use RuboCop, which has an add-on for enforcing sigils on top of files.
00:19:46.480
Moreover, Shopify provides a separate tool called Spoon that automatically increases the strictness of files when it's confident that no issues exist.
00:20:12.320
After setting up the initial steps, the main focus for us became evangelizing Sorbet and educating team members. Thankfully, many developers were curious and eager to adopt types.
00:20:57.920
We held presentations and discussions, and after addressing any issues, we decided to enforce Sorbet on our continuous integration system. Everyone was on board with the idea.
00:21:37.440
Stats generated by the Spoon tool reveal that only eight percent of our code files remain untyped, with seventy percent of our method calls invoking typed code. While we've made significant progress, there is still much work to do.
00:22:18.720
An example of dealing with dynamic code arises from our service objects, which encapsulate complex logic or actions with side effects. These are typically initialized with parameters, followed by a public call method.
00:23:04.000
To enhance code reusability, we included a helper to allow calling service objects via a class method. However, Sorbet struggles with this dynamic approach since it can't infer types from dynamic constructs.
00:24:02.239
To overcome this, we explored options for generating RBIs but quickly discovered that re-declaring types for every service object would be cumbersome. Thankfully, we found the library Parlor, which automates this process seamlessly.
00:24:51.680
After implementing this solution, we can now generate dynamic RBIs for all service objects, retaining clearer typing in our codebase. While we had to remove some dynamic imports and reconsider other libraries like Dry Monads, this shift has yielded better results for us.
00:25:50.000
Switching to Sorbet has allowed us to neatly replace constructs from Dry Monads with Sorbet features, ensuring our code remains comprehensible and maintainable.
00:26:17.920
While automation has improved our process, the current challenges include generating RBIs, maintaining dynamic imports, and automating various checks to ensure everything is consistent.
00:26:46.080
Runtime checks need further refinement, but we’re aiming to gradually implement them as we type more code. The journey with Sorbet has taught us that while gradual typing allows for flexibility, caution is required.
00:27:33.360
The inspection capabilities of Sorbet aid in understanding type returns and implementing better practices. Nevertheless, Rails' dynamic nature can still complicate interactions.
00:28:07.680
Keep in mind, many experienced Ruby developers may take time to adjust to a typing paradigm.
00:28:12.960
Lastly, it's important to note that Sorbet is still a work in progress, consistently refining its features and addressing evolving needs. We’ve faced various breaking changes with new versions, but we've remained committed to its use.
00:29:03.119
After two years of utilizing Sorbet, the team has recognized its value, despite any challenges faced. The parts of our code that now benefit from type checks have reduced the kinds of issues we're encountered before.
00:29:32.959
Finally, we're hiring! We've experienced significant growth in the past year, doubling in size and expecting that trend to continue, all while maintaining a great work-life balance and inclusive remote environment.
00:30:06.080
I want to share candidly that this has been the best employer of my career. If you're interested, please visit grailed.com/jobs or come talk to us at our booth.
00:30:33.920
For those seeking scholarly insight, feel free to browse our recommended research papers on static typing.
00:30:48.960
Thank you so much for being here today! If you have any questions, please come talk to me—I'll be here. Thank you!
00:30:55.680
So much.