 Thank you very much for coming, all of you, I know it's the first talk after the lunch on the last day, so I'm pretty surprised that there are still people going to talk. So thank you very much for being here today. It's really, really an amazing opportunity for me to be here because it's RaceConf and because I work for a company that somehow was born at one of the RaceConf. So it's really, really super exciting for me to be here. Today I'm going to talk about developing and maintaining a platform with Race and Hanami. As you can see, the title is slightly different than what you have published on the program because by the time I proposed the talk and the talk was accepted, the framework changed the name. So that's why you can see Race and Hanami today. So sometimes I will mention Lotus, but actually Lotus is Hanami today. So keep in mind that's the new name. Now yesterday, I was struggling, I was trying to find a way to break the eyes because normally I'm really, really super nervous when I am on stage. And so I was talking with my coworkers and I was like, what can I do? And they were saying to me like, we have candies. I was like, yeah, but I cannot throw candies on the stage and I was like, why not? So I want to start the talk when I make sure that all of you are focused and getting my attention by just playing a new game, which is throw and catch the candies. So hopefully I will not hit anybody. Here we go. You can take some of the candies. Here we go. And we have more candies over there in the front here, here and over there. Okay. We have more after. Don't worry. Don't worry. Just stay focused. That's the goal. You know, you have to stay focused. You have to take candy when I'm throwing them. Okay. Great. Let's start. So my name is Simone. I work for DnSimple. My nickname is WebPost, pretty much everywhere, Github, Twitter. And I work for DnSimple, which is a company who provides domain name registration, DNS hosting and SSI certificates. Now you might have or not heard about us, but probably you heard about some of our amazing customers. So we provide service to RubyGems. So if you installed any of the gems, you use our infrastructure. We provide service for RaceConf and many other services, including Travis CI, for example. Now before I start getting into the core of my talk, I have a very important public service announcement. This is not my birthday today. Now 99% of you won't get this joke, but at least two people over there will get that. So you can ask explanation after that. But it was really important for my safety to say that it's not my birthday today. Okay. Let's get started. Today, I will stop laughing. Today I want to tell you a story. And this is a story about a feature we have been working on for at least a couple of years at DnSimple. So what I want to do today is I want to share the decision that we made during the development of this feature. And I want to share with you some of the behind of the scenes that facilitated the adoption of the decision that we made. Now the story is about a new component or system which is the new version of the API, API version 2. Now it's not going to be another talk about building the API with Race or any other framework. If you came here with the idea of not hearing about building API, that's the right talk. But of course, I had to spoke about some experience and our experience would be close enough to the idea of the API. Now we have been providing API in DnSimple since the very beginning, since the 2010. That's a really core component of our service. And our customers use our API for a variety of reasons. For example, registering domains or provisioning new DNS records, for example, through Chef or purchasing SSI certificates. Now the way people interacted with our infrastructure back in 2013 and 2014 was, I'd say, a traditional way. So we had the DnSimple application and we had like a normal standard set of traditional API. But we wanted to do something different. We wanted to extend, to provide, to shift the focus from this standard set of API towards an idea of a platform, a toolkit for building things around DnSimple. And so in 2016, actually in 2014, we started re-engineering our API. And so as of today, we still have the standard set of API and application. But we also started offering OAuth. We started offering webbooks and official clients in different languages. Now the interesting part is that besides the clients that are, the official clients that are now developing different languages in addition to Ruby, pretty much the rest of the things you can see here are designed and built with Ruby, okay? So now that I shared a little bit more about the background, what I wanna show you today is essentially three different messages. The first one I wanna share with you today, the DnSimple approach with Rails. I wanna present you the Enami framework, which is the framework that we used in order to build this new part of DnSimple. And I wanna show you how you can use Enami or any other framework to interact with Rails. This is actually possible. So let's get started with the first part of the presentation. The DnSimple approach to Rails. Now our approach is a little bit different than the traditional Rails way, I'd say. And the reason is because we have a pretty large application. And so it's not really uncommon as soon as your applications start growing beyond the standard Rails blog application to face huge need of having a lot of different needs within the same application, like talking with external services and so on. So if your application starts growing, you will likely hit the same kind of troubles that we did. And so one of the key principle for us was to be able to write code that we can maintain, okay? We have been in business for six years and we hope we'll be in business for even more years. So the idea is that we need to have code that can be maintained for a long time. So you cannot really change, you cannot really have full control of your code unless you write your own code, right? I still remember the days when the transition between Rails 2 and Rails 3 make a difference between the way that JSON was serialized. So before we had the root, the model that was the root of the JSON. And then suddenly from Rails 2 to Rails 3, everything changed. But you can't really go back and change, like in one month, the API that all your customers have been used, right? So what you want to do is that you want to be in control of your code. And for us, Rails is just another component that we want to be in control of. So what we do essentially is that every dependency that we use in DnSimple is wrapped behind a custom API. So this is a very super simple example here. In some part of our code, we need to deal with phone validation. So we use a library which is called phony that perform like validation and normalization of phone numbers. And rather than going and calling phony directly, what we did is that we wrapped the phony library behind some custom code. This is a DnSimple phone module. Now, the very interesting thing about that is that at some point, this is the final code that you see. At some point, we had to introduce some wide-listed numbers. And the reason is because phony was not catching some particular countries, was not able to understand those country phone number formats. And so what we had to do is that we had to introduce a wide list. And that was super simple, super easy to be done, because we just introduced the wide list within the module. And we didn't have to go and change all the previous code I was using the library directly. So this is a very super simple example of how you can improve the maintenance of your code just by writing a little bit of extra code rather than going directly with a gem. So some of the advantages of this approach is that side features or extensions like the wide list can be introduced without really changing the code base. Incompatibilities can be addressed in a single place. And if you need to replace the underlying library, you can do that in a single place rather than going and changing all the single method codes. And another really important component and advantage is that testing doesn't require extensive stuff. Now, I don't know what's your code, but our code was used and still uses a large number of steps. And this is not really extremely maintainable in the long run. So this approach is much more flexible. And in fact, here's an example of another component we have, another library we have inside our code where we can avoid using stopping. So here we have a resolver, which is one library that deal with our custom DNS record called the alias. And that performs the resolution. We have adapters behind the scenes. We have another adapter that essentially discards all the requests. We have a test adapter that is what we use in testing. So rather than stopping, we can flip to this adapter and we can feed the adapter with the response that we expect in the test. And then we have the go production adapter that will rely on our Golang implementation of the alias record. And this is turned on by default in production. And this is an example of how we use that in the test environment. We can simply switch to the test mode and we can feed the adapter with the kind of response that we expect, and then we can test that. And we have the full control of our code. We don't need to use mock because we don't need to mock the original libraries. That's a really huge advantage. Of course, another component that we have to deal with that is ActiveRecord. And in that particular case, we have special guidelines for ActiveRecord. So here's a few ones. For example, methods defined in ActiveRecord base, like find, where, and so on, cannot be used outside the model itself. Instead, the models must expose a custom API to perform operation. And we call them the finders. So we have specific objects that are called finders. Callbacks are not allowed anywhere except for that integrity. And the query methods in ActiveRecord cannot be used outside the models or the finders. So any code outside the model, sorry, any code in the application, only interact with those finders. And finally, scopes can be invoked directly, but they are used just to compose queries within the finders. Here's an example of a finder. We have, for example, an implementation, a custom implementation for the finder. I don't know how many of you had the problem that the default find version uses the auto-increment key of the database, but sometimes you need a string. You need to search for a string. So you don't really want to override the default find because other libraries or plug-in might rely on that specific behavior. So this is an example of a custom finder. And then this is an example of a different finder that use a variation of an input. For example, we want the TLD to be not case-sensitive during the query. Another example are default scopes. Another example are default orders. You don't really want to set default orders in the model because in other places, you will have to de-override the defaults. And then another example here is when you want to have queries that instead of returning active record relations, returns, for example, an array of elements. Now, all those kind of methods. And then we have the last one, which is a specific implementation of the not found where we raise a custom message rather than the default one in active record because the default one in active record includes information about the query. So for example, the web parameters. Now, all these code allows us to be independent from the underlying implementation of active record and improves our maintenance. Of course, these guidelines are the result of years of experiments and discussions. They are not really in stone. And they keep changing and evolving within the team based on the experience we have. And that's why I also want to take a few seconds to say thank you to all the team members of the UnSimple for contributing to these guidelines and for contributing to the quality of the project. Oh, wow, we have another candy time. So more candy here. Was I expecting that? Over there. Here we go. And here, there, candies. And who was missing candy? Oh, yeah. Here we go. Instead of presenting, we go and play baseball. Okay, awesome. Let's move on. Next step is what I call the MVC Plus. It's essentially a different approach that we have in the UnSimple to structure our application. Traditionally, if we don't consider the view part, traditionally, your application have two layers, you know, the model and the controller. But in the UnSimple, we essentially have four layers, right? So we have the controllers, we have the models, and in the middle, we have commands and services. I'm gonna explain a little bit what are the needs for these extra layers. Now, this is a request, okay? The user is performing an HTTP request, and the request can either be an API request or a normal request from our browser, right? So the request is sent to the controller. The controller handles the request, and it can be a race controller, or it can't. I mean, we don't really have to, but let's pretend it is for now. Now, the controller handles the incoming request, incoming request validates the input, parses the parameter, stops more from request, authenticate the request. But what's important is that the controller has no business logic at all. This is an example of a controller, so you can see, this is a typical race controller. We have the new action, we have the create action, and as you can see, it's really, really small and tiny compared to the normal kind of controller you probably have seen in our application. And just for you to know, this is the real code from the domain create controller. So it's essentially, domain is one of the core components in our application, so you can imagine that creating a domain is one of the most complex part of our application. Still, as you can see, the controller is extremely readable, extremely short in terms of line of code. What it does is it delegates to a command, passing the parameters that we expect, gets some data out, and then if the response is successful, then renders the page, otherwise renders another different page where we ask the user to provide more information. That's it, that's all about the controller. Then the next layer is the command. Now, commands encapsulate the user business logic for us. So we have specific operation that the user can perform, and in fact, other companies, other libraries have a similar notion and they call them operations. Now, we don't call them operations because of a historical problem, but yes. So that's essentially commands. You can think them about operations. Now, command can only delegate to lower level. This is more or less the same rule of controllers. You have never seen a controlling race calling another controller. That's the same for the command. In our application, a command can only go down beyond in the path. It cannot call another command. And this is an example of a classic command, the same command that you saw before. As you can see, there are some includes that creates the underlying structure of the command, and then you have some preliminary first level business logic validation. So here's where we have the first level business logic validation. Then we have permission. We have rows. After the domain successfully create using a service, I will talk about service later on. After the domain is successfully created using the service, we perform some extra operation. For example, we track the activity of the creation of the domain. We send out notification about the creation of the domain. All those kind of things that you are probably used to see as callbacks in a model. But I can tell you that as soon as you have, like dozens of these kind of activities within the model, it will be a huge nightmare to test them and to maintain them in your application. The command never raise, commands never raise errors. Okay, what they do is that they have to return a result. The result can be either successful or unsuccessful. And if the result is unsuccessful, has an error message. And even more importantly, the error message has to be a user-friendly error message, not a kind of exception message. So in case we have an exception there, we'll rescue that and then we try to provide a meaningful error message. Then we have services. Now the services are the core of our business logic. Each service is essentially a public, contains public methods that represent the business unit, the single unit of operation that can be performed in our application. So example of services for us are the domain service, the two-factor authentication service. An example of public methods within those services are two-factor authentication.enable.disable.authenticate, and so on. So you can think about every single method is essentially a small unit, a single action, that a single API that our application exposed to the external environment. Now another interesting thing is that services are, I'd say, almost functional, in the sense that they don't store any state. Services don't have any state. And this is really important for us because it's really a readable implementation. Services get an input and then transport the input and return back the output. No internal state. This is an example of a service. So as you can see, the creation of the service uses dependency injection to get some of the other services that the service is interacting with. And this is really helpful when we have to isolate each service in the test suite. And then we have several different public methods. We have the create, we have the delete, the lock. And this is one of the examples. So the creation, the creation what it does is that it interacts with the models, takes some attributes and then creates the object and then interact with other services. That's the other thing. That's where the interaction happens. A service can interact with other services. And after that, the service is expected to return a result. And of course, services can raise exceptions and those exceptions are expected to be catch in the commands. This is another example of the service. This is a zone service that was used by the domain service. And as you can see, a method can call other methods. So for example, here we have the creation of the zone that uses the creation of the system record and the refresh of the zone. Because as I said, you can expect every single method to be a single task, a single operation. And so that specific operation can be reused elsewhere in the application itself. But it's really important to code that specific component in one single way so that we can test the code in isolation. And then finally, we have the models. Again, in our case, models are still active record models, but they are a little bit different. So they are essentially an abstraction for the storage engine. And they contain only methods that are useful to persist information to the database. Models should have no business logic. A model should not write to other models. Or trigger other model actions. And then we don't use callbacks, as I said, except for that integrity. This is an example of a model. As you can see, for example, take the move to account method. Now, the move to account method, as you can imagine, is shifting a domain from one account to another. It's a pretty huge task within our application because it involves a lot of business logic behind. We have to change the billing information. We have to increase the number of domains. On that particular account. But as you can see, there's no code here that deals with that. Everything happens in the service. Here, we only deal with the persistence of the information within the database. And that prevents a huge model, like the domain one, to become like thousands and thousands of lines, including every sort of kind of operation, including, I don't know, calls to external HTTP services, for example, which don't really belong in a model. So at some point, at this point, you might be wondering why? Why we should have all these level of complexity? And in fact, if you are dealing with smaller application, you probably don't need all these level of complexity. But still, you might want to have something in the middle between the models and the controllers that have stores the logic of your application. Now, if we look, if we just consider the commands of the services and the models, you can actually understand that you have, you essentially coded the entire business logic of your application into an environment which is completely separate from the context of an HTTP request. And even separate from the context of a race controller. And we actually took that one step further. So the core is codified and easily maintainable in a dependent set of libraries. Because after all, our models are still active record models. So we don't have control over those libraries. Because they rely on active records. So active record can make changes behind the scenes that will affect our application. So our code, the code we rely on, the business logic is essentially coded within the commands and the services. So that's the reason why we have this kind of abstraction. And so that allows us to start shifting the focus and introduce different kind of controllers. So for example, the controllers can be raised. But then you can also introduce, I don't know, Hanami. Or you can introduce any other framework. At that point, once you have that specific isolation of your code, you can easily introduce something, more things in the front. And you don't have to refactor. You don't have to change how the code of your application work. So this is essentially a super simple, simplified example of our infrastructure for the DN simple application. As you can see, we have raised on the front. We have another framework, which I will talk in a few seconds, Hanami. Then we have the commands. We have the services. And we have the models. And you can see the flow of a specific request coming from the controllers, going through a specific command, a single command that will delegate to one or more services. And then each service will interact with the models in order to accomplish the task. So next step, Hanami. I mentioned Hanami a couple of times during this talk. And I want to talk a little bit about Hanami and why we use Hanami and what is Hanami, first of all. So Hanami, as I say, was formerly called Lotus. And it's an emerging Ruby framework that was created by a friend of mine, Luca. Luca designed this framework in a way that encouraged developers to use good development patterns. And you can see that, not only if you use Hanami, but even if you check how the source code that Hanami was implemented. Now, Hanami is not 1.0 yet, but it's really stable. And it's really mature. We have been using that in production at DN Simple for almost one year so far. Hanami is not just a simple single task framework. It's actually a full framework. It's made of different components. So you have the application, you have the router, you have the actions, you have the views, you have the models, you have the migration, the helpers, the mailers, the assets, and the command line. So it's pretty much similar to RAIS in terms of focus, in terms of components. And the good thing is that you can just use some of them. You don't have to use all of them. And in fact, in the DN Simple API, we use the router, and we use the actions. Some of the key features of Hanami that I love are modular architecture, which means you can use the components you want. You can use all of them or just a few of them, even in an existing application. Design based on composition that encourages you to use object-oriented encapsulation, so good development patterns. And even more importantly, test-friendly approach. Given the good design behind the structure of loaders, you can really test specific loaders component in a super easy way. So the question, one question is, why the DN Simple adopted Hanami? And rather than going through different frameworks or just staying with RAIS, for example. So RAIS provides, as you probably know, a way to format the same response in different ways. You can return from a controller, you can return a JSON response, you can return an HTML response, or whatever. Now, the problem is that this approach is not really maintainable for large and complex application. Because you might have, for example, changes in the view that will affect the API. So you don't really want to do that for really large application. So the choice at this point is either to create new controllers. So clone your controllers and have those controllers just to deal with the API, or go with another alternative framework. And because RAIS is a massive huge set of libraries, and because we wanted to have control of our code, we decided to not create more RAIS controllers for that. So the normal choice at this point is Sinatra, as you probably guess. Sinatra is a nice framework, but I have a kind of hate-love relationship with it. Because it's super nice, especially for prototyping. But as soon as your application starts growing, and you have 30 different routes, or 40 or 50, and then you have helpers, then you have formators, and so on, suddenly Sinatra, your Sinatra application will be really hard to maintain and really hard to test. So at that point, Luca was starting actually a few months before we started dealing with that. Luca started his adventure in building a new framework called Lotus. And I knew Luca since a while, how I was working with him in previous projects. So I decided to give Hanami a try. We originally started with Sinatra. So we implemented three, four API methods in Sinatra. And then in less than one day, I changed, actually, in a few hours. I switched, and I implemented all those methods in Hanami. And I had them running in parallel for a while. I was running tests, I was running benchmarks, and that was extremely stable in the way we could test. And to be true, to be actually, I didn't even test the Sinatra controllers, the Sinatra implementation, because it was so hard. And at that point, I was just prototyping the new API. Whereas when I switched to Lotus, the testing was so easy that along with the library, along with implementation, I was able to write tests. So at that point, we realized that Hanami was working really well for us. And that's why we decided to stick with that and essentially build the whole API v2 and that part of the platform with Hanami. For Candice, I see more and more people sleeping. So it's time for Candice. Yeah, there. I'm missing someone? In the front? Oh, here we go. Awesome. OK, last part of the presentation, how we use Hanami in parallel with Lotus. Now everybody's looking for Candice and nobody's taking attention to my presentation. Awesome, well done. Good job. OK, so the architecture that I presented before helped us to reach this goal, because as you saw, we split apart the controller part with the rest of our infrastructure. So we didn't really have to completely develop our code to deal with Hanami. So Hanami is essentially a rack application mounted on the race controller. We still use the main, the primary race controller to orchestrate the Hanami application. As you can see at the top, there is the race routing file. We have a scope for the API version 2. Actually, we have a scope for the API and we have a particular scope for the API version 2. And then we have the routes for Hanami. So Hanami has its own router, and that's where we define the rules for routing requests within the Hanami framework. I don't know if you can see that. You can see that. So here we have in parallel an example of the same controller implemented with race and implemented with Hanami. So on the right side, you have the Hanami implementation, which deal with the API. And on the left side, you have the race implementation, which is essentially our front-end application. As you can see, the code is pretty much the same. And so because of the different layers that we have in our application, and because of our controller being so small, it was really easy for us to start implementing the API with Hanami. As you can see, we call the command. We have custom objects in order to deal with the parameters. We have custom serializers, as I told you before, because we love to be in control of our code. So we don't use any Hanami-specific or race-specific library for dealing, for example, with parameters or validation or the serialization of the response. We want to be in control of that specific part of the code. And this is an example of how a controller, an Hanami controller, is structured. And it's actually pretty really interesting, the part where in Hanami, every single action is a class. Sorry, it's an object, right? So we don't have a single controller, like in race, where every single method is a different action. And you have to deal with public methods that are interpreted as actions and private methods and so on. So in Hanami, every single action is a class. And the class has to respond to a method called call. And this is where the logic of the application happened. And it's pretty interesting, because if you have a specific action that has to deal with some non-default operation, you can just create a method, or you can just override the standard implementation of the action. And that will be. In this case, you can see we have B4 filters. So we do validation of the subscription, whether the account is in good standing before performing any kind of operation. And here we have two actions. One is to enable the wish privacy, and the other one is to disable the wish privacy. And Hanami also has a nice DSL to configure the controllers. So this is the example. You can include modules. Modules are essentially just for the code that you can reuse. And for example, here we have shared information, shared methods across all the different controls that we use for validations. OK. So wrapping up my presentation, my message today for you is, first of all, experiment. This is one of the key principles in DnSimple. And we like to experiment new ideas to try a new framework, to experiment new approaches. And you should do the same. Take the time every once in a while to do some research and development, because you might be surprised how many new technologies and interesting emerging technologies you can find out of there. The second tip I have for you is, get influenced by other communities. So I'm here today in a race conference talking about race and other frameworks. And the talk, after mine, will be about a completely different language. So you have to be influenced by other communities. And it can be other frameworks, or it can be communities built around other services, services you use. For example, we had an amazing experience when we started dealing and writing code that was interacting with GitHub or even Slack, for example. By the way, speaking of Slack, I have a very super quick announcement. For all of you that are DnSimple customers, we just released Slack implementations, Slack add-ons, so you can now manage all your domain through Slack. And that will interact with the DnSimple account. That was actually a screenshot I took a few days ago. And as you can see, it's DnSimple.slack. But today, we got the news that the Slack integration was approved in the Slack directory, so you can enable that directory from Slack as of today. The other tip I have for you is, this is a really important tip, get influenced by other programming languages. So in the last month, in the last four months, I personally worked in four different languages in parallel to build all the DnSimple API clients. So I worked in Ruby, in Go, in Alixir, and a little bit of PHP. And so that was really wide opening for me because interacting with other languages, working with other languages, made me a better Ruby programmer. I was able to deal with assumption to, I had to deal with information that was coming from our API, from our application, that were based on Ruby assumption. And I had to face with the problem of dealing with that from other different languages. So every once in a while, be sure to take a look at different languages because they will be, even if you won't use those languages in your primary daily activity, they will make you, in that particular case, a better Ruby programmer. This is a credit for the picture at the beginning of my presentation, and that's it for today. So thank you very much for being here. If you have any questions, maybe to ask me.