What advice/rules I may give to junior developers about the Ruby on Rails app design?

Do not fear using pure Ruby objects & design patterns

  • Try to follow single responsibility principle. Create objects that are highly specialized and do one thing only.
  • Try to keep Rails controller responsible for handling requests only and delegate the actual logic to other objects —Service Object and Query Object patterns are very useful here.
  • For behavior splitted into multiple objects try to use Facade pattern to centralize object orchestration.
  • Be aware of common Rails design patterns — Value Objects, Service Objects, Query Objects, Presenters, Repositories, Form Objects. Use them to increase cohesion of your solution and achieve low coupling.
  • If your domain model and database model differs (like domain-wise you have one thing but it is represented as multiple AR models, or database field layout is wildly different than domain sense of the object) use heavier Entity/Aggregate objects with Repositories.

Favor explicitness over implicitness

  • Avoid implicit features like accepts_nested_attributes_for or deep relationship chains in your AR models.
  • Avoid using after_create/update/destroy/commit callbacks — they are powerful mechanisms that are a little too powerful and are sources of subtle bugs. Also, they make reasoning about the code very hard.
  • Keep your side effects together — do not use ActionMailer through the ActiveModel method, instead call it in a service object instead. Reading the ‘main’ function of the business use case (usually query/service object) should reveal all side effects immediately.
  • Follow the law of Demeter — keep your object chaining minimal.

Split your application horizontally

  • Use modules to horizontally split your app — avoid splitting vertically (like Application::Something, Domain::Something, Infrastructure::Something).
  • Follow the business language (Ubiquitous Language) when naming things. If in doubt, ask the business expert how they call the thing you want to name.
  • Namespace horizontal boundaries using Ruby modules — in e-commerce you’d propably have Order module, Shipping module, Customer module and so on.
  • Calling a module from another module should be forbidden if they are distinct. Calling a submodule from the parent module is ok (like calling Order::Confirmation from Order).

Design for real non-functional requirements

  • Always ask what load is expected — how many users do we expect to use this particular feature, how often, how critical it is. Make decisions about implementation based on this data.
  • When in doubt, measure. Use tools like New Relic or similar, perf on Linux, ruby profilers to understand the performance profile of your app.
  • Do not optimize prematurely. Usually more performant code has a trade-off in readability and maintainability — try to avoid it if not needed.
  • Be careful with external integrations. Use infrastructure-level patterns like circuit breaker or bulkheads to handle failures robustly.

Test your code exhaustively

  • If you need to use mocks, think twice. Use dependency injection if you need to inject an external dependency to your tested object.
  • Unit is not necessarily one object only — it can be multiple objects.
  • Be sure to write tests for every case — if you have a conditional, be sure tests run both paths in your code. Test coverage tools can help a lot here.
  • When in doubt about architecture, start with functional (request, controller) tests first. You may leave tests on this level if there are no other clients using the same objects. Move to lower level if you happen to reuse an object — it deserves its own test then.

Favor composition over inheritance

  • Rails-specific corollary to it is that STI tables are considered a code smell and should be avoided. Use them only for performance reasons, if any.
  • Make use of dependency injection heavily to allow composition to shine. It’ll help with the previous point as well.

Respect the control flow

  • Avoid deep conditionals. Linux kernel developers do not need more than three levels of indentation and they are writing extremely complex software with many corner cases.
  • Push conditionals to the highest possible layer of the application. Most decisions you are doing are based on HTTP request parameters — you can create proper objects on the highest layer and avoid doing these decisions again. For more details this may be a good read — replace ‘lambdas’/’functions’ with ‘objects’ in your head.
  • Think in layers — do not call application-level object like service object from domain-level object (like Entity/Value Object). You should have at least three layers in head — Application (which is allowed to orchestrate between horizontal boundaries — that’s how you call stuff from different modules), Domain (where the business logic lives, split by the horizontal boundaries) and Infrastructure (external API integrations, adapters for external services like APNS, database). The order matters — you only go left to right with your dependencies. This will ensure your flow is linear.
  • Avoid communicating between objects at the application layer — they are considered to be self-contained and should not be a ‘building block’ — this is what domain layer is for. If you need to call two services or two queries, use Facade pattern (often called Command in this particular context).

Conclusion

--

--

--

React.js evangelist. Writer (4 books, Frontend-friendly Rails being the newest one). Dreams to make software more reliable and just _better_.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Jibaku — Android Self-Destruction/Reset to Factory

Exceptions in Java

How we achieved 99.99% availability rate thanks to smart retry 🚀

Object-oriented programming: a simple concept, explained simply

Episode 1: Face to Face

Hive Blockchain and WordPress — Best of All Worlds

HIVEWP.png

A Quick Method for Generating Long Code Scripts that Every Analyst Should Know

Slack notification in Jenkins pipeline

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Marcin Grzywaczewski

Marcin Grzywaczewski

React.js evangelist. Writer (4 books, Frontend-friendly Rails being the newest one). Dreams to make software more reliable and just _better_.

More from Medium

All the Code I Didn’t Write

Using esbuild with rails 7 in a simple way

STOP drying up your code! — The overuse of DRY

How to initiate Refactoring?