Gharbi Mohammed

Component-Based Rails Applications

Ruby Unconf 2019

00:00:51.390 Hello everybody! Thank you for being interested in my talk today. I hope you will like it.
00:00:59.079 My name is Mohammed, and I'm originally from Morocco. I used to live in Spain for eight years. Both of those countries are warm, unlike here. The most important thing is that I'm really excited to be here with you all. You guys from the north seem to enjoy being in the south, while I, being from the south, am very excited to be in the north.
00:01:16.420 I moved to Germany about a year and eight months ago, and I work for a platform for event locations. Today, I will be talking about component-based applications.
00:01:40.630 In the beginning, I will explain some obstacles that one can encounter when using a monolithic application and how we can overcome these issues using namespaces and engines. In our case, we will go in-depth with engines as the best solution and how they will support the application.
00:02:00.479 In the second part, I will cover how we can benefit from this architecture, speed up our deployments, decrease memory usage, and deploy our applications on separate servers. Many projects might fit well with typical monolithic Rails applications, but what happens when they reach a certain size? It can become increasingly hard to understand what all the little parts do.
00:02:25.130 One solution to this issue is to use namespaces. For example, you can organize modern controllers, such as admin controllers in one folder and API controllers in another folder. This organization can extend to helpers, JavaScript, and services, allowing you to enforce a dependency structure, which is a solution offered by component-based Rails applications.
00:02:36.830 From the first look, the impression given is not the typical Rails application structure with the app folder and config folder. What I really appreciate is the unique structure that comes with this approach. If we go deeper, we will find two main folders: engines and web container.
00:02:55.250 The web container will contain our main Rails application, but nothing elseā€”just the configurations necessary to run the application. All the logic and code belong in the engines. For instance, we have different engines for the admin UI, API, dashboard UI, and domain logic that contains our models.
00:03:20.290 So, what is an engine? An engine is a mini Rails application, essentially a gem that can be mounted inside your main Rails application. To create a gem for this, you can run a specific command which will create this mini application inside our engines folder.
00:03:37.100 As you can see, it's similar to a normal Rails application, but it cannot work independently; it must be mounted or integrated into our main application to effectively utilize external gems or engines.
00:03:50.960 To do this, you simply add the engine gem to the gem file of the main application, add the dependencies in the gemspec file, and ensure that any required gems are manually included, as they are not automatically loaded in this context.
00:04:03.220 Now, let's discuss migrations. When you create an engine, you have separate modules, and we create our migrations, but how does our main application recognize them? You can run a specific command which will copy your migrations from the engine to your main application.
00:04:22.360 Alternatively, you can define the migrations in such a way that they are automatically run when you invoke the standard rake db:migrate command, eliminating the need to manually copy them.
00:04:48.240 One of the interesting aspects of this structure is that we don't run all tests together. We run tests separately for each engine in isolation, ensuring that each engine contains a dummy app specifically for executing its tests. In the Rails helper, we require the environment file from our main application.
00:05:12.260 We also create bash files in each engine that run the necessary commands to prepare the testing environment, like running bundle install and preparing the database. In the root directory, we have another bash file that executes all tests across the engines, ensuring all tests pass.
00:05:55.000 Now let's integrate an engine into our main application. You simply call the gem by its name in the Gemfile from the engines folder.
00:06:03.120 You can mount it easily, for instance, to mount public UI engine at the root, or to mount the dashboard UI at the corresponding path, leading to a clean routing file. This structure maintains readability, especially as the application scales up.
00:06:30.020 The benefits of this component-based architecture complement good object-oriented practices by enforcing an internal dependency structure. We achieve a cleaner Gemfile and cleaner routes, which result in faster tests. Running all the tests together can take significant time, potentially 20 or 30 minutes for larger projects; however, we run them separately in this architecture.
00:07:22.083 Additionally, in terms of team collaboration, consider a developer working solely on the admin UI; they can focus just on that part of the project without worrying about the other components, benefiting from isolation in development.
00:08:11.359 Up until now, we've seen what engines are and how we can integrate them into our application. Now let's delve into how we can further benefit from this architecture in terms of memory usage and deployment.
00:08:39.269 To illustrate, let's review an example. We are familiar with grouping our gems in the Gemfile and installing specific ones for development or production. In this case, we can replicate that by creating groups for our engines, such as API, public, and admin.
00:08:55.420 By doing so, we can install and load only the necessary gems for the specific group we are working in, thus optimizing our deployments. For example, when deploying, one would install only the admin gems without including API or public gems.
00:09:14.140 This method utilizes an environment variable in our application to dictate which groups to load. If an engine depends on another engine, it must be defined in the gemspec file, allowing access to the necessary classes from different engines.
00:09:32.560 This modular approach allows for flexible deployment across various servers, thus enhancing the application's scalability and performance. As a practical example, we have previously benchmarked memory usage in applications loaded with all engines versus those limited to a specific engine.
00:10:19.260 The results showed significant differences, illustrating how well-structured engines can optimize resource utilization. For example, the popular Devise engine, known for its authentication capabilities, exemplifies this approach by compartmentalizing functionalities into engines.
00:10:40.000 Additionally, I have a repository you can explore to see how this concept is applied in practice. Now, before I conclude, I would like to share a personal project I'm working on.
00:11:10.360 I'm currently running a project to build a school in San Sonia. I visited the area last December and met with families who are lacking access to kindergarten and school facilities for their children. If you would like to support this initiative or donate, please check out my story and feel free to approach me for more information.
00:11:58.959 Thank you very much!
00:22:35.240 If you have any questions, feel free to ask.
00:23:16.710 Yes, if from the public UI I can access classes from another engine, like logic, the answer is yes. This is defined in the gemspec file that dictates dependencies.
00:23:30.830 Please feel free to approach me if you are interested in the architecture or if you have any questions.