Skip to main content

[gem] Trailblazer

1. Why should use Trailblazer?

1.1 Fat models in rails

  • Experiences from the past decade have taught us that it is a not-so-good idea to push all kinds of business logic into your model layer.
  • In Trailblazer, the model class becomes the place for persistence , only, making the model a low-level data object with a very simple set of functions: Retrieving and writing data to a database.

1.2 Hard to navigate files in Rails apps

  • Rails organizes the implementation of your domain into models, controllers and views. Even though this code belongs into the same logical group, the actual code files are spread into different directories and separated by technology. In Trailblazer, you structure your app by domain into real components

1.3 Unclear high-level domain implement in rails

  • While Rails does provide a high-level domain in the form of controller endpoints, the implementation thereof is fuzzy. Is it implemented in the controller? Or the model? Or a service object and the controller?

2. Trailblazer structure

3. High-Level Domain

  • As an example, this could be the following typical session

    1. Visit the Trailblazer page and read the summary.
    2. Write a comment using the page’s form and submit it.
    3. Click the “Follow Trailblazer” button as you’re interested in the further discussion.
    4. Go to your dashboard and see new comments for “Trailblazer”.
  • The “functions” are what I call the High-level Domain in Trailblazer.

  • In Domain-Driven Design (DDD), this is called a Use Case .
  • In CQRS, this kind of “function” is called Command.

3.1 Implementation of a high-level function

  • In Trailblazer, the implementation of a high-level function goes into an Operation. Surprisingly, this is a class !

4. Controllers

4.1 lean HTTP endpoints.

  • Controller only knows about the HTTP layer
  • They handle authentication (if not handled elsewhere), differentiate between request formats like HTML or JSON and then instantly delegate to the respective operation. No processing logic is to be found in the controller.

4.2 render views or delegate to Cells

  • Controllers can render views or delegate to Cells (a.k.a. View Models) and provide a better abstraction for rendering complex UIs.
  • Controllers might also use responders to render responses.

5. Operation

  • An operation can also be used from the console or as a test factories.

  • The real power of operations comes with the validation, processing and post-processing of the data, and Trailblazer structures this workflow in a pipelined fashion.

  • note

    Note that putting classes into classes is simply namespacing and doesn’t involve inheritance or any sort of coupling.

  • Keep in mind

    An operation always has to implement one method: #process. This is where the implementation of that use case sits.

5.1 Form

  • Contains all the business logic.
  • Every operation validates its input using a form object.
  • Operations and forms can change and persist models.

5.1.1 Contract

  • Contract is a schema of the data structure
  • Contract can be used for deserializing (and serializing) incoming documents

5.1.2 Reform

  • Reform uses ActiveModel::Validations, you can use all kinds of standard validations like length or inclusion.
  • The contract is simply a Reform object.

5.2 Representer

  • Representers can be used to parse incoming documents and to render API representations.
  • Since form objects internally use representers they can be used for that directly without having to specify a representer.
  • Operations replace test factories, and can be used in scripts and console, too.

5.3 Model

  • Lean persistence endpoints.
  • No logic but configuration is allowed in model classes.

5.4 High-Level Architecture

  • Structuring and implementing the typical requirements of a web request in Trailblazer is called High-level Architecture and is the missing piece of frameworks like Rails.
  • Without a high-level architecture, developers often complain about missing structure in their code and problems that arise from lack of encapsulation.

5.4.1 Deserialization

  • Deserialization means transforming the incoming request data into an object graph. This happens with a representer.
  • 序列化 (Serialization)將物件或數據結構轉換為字節或字符,以便在網絡傳輸或保存到本地硬碟等操作中使用。換句話說,序列化就是將資料轉變成二進制的檔案或者是字串。
  • 反序列化 (Deserialization)則是將這些字節或字符轉換回物件或數據結構,以便程序能夠對其進行操作和處理。也就是說,將收到的二進制檔案或者是字串轉換成原本的物件,這就是反序列化。

5.4.2 Validation

  • Validation we call the process of verifying this object graph is in valid state. Validation’s done by the form object.
  • In Trailblazer, we often say contract instead of form.

5.4.3 Persistence

  • Persistence will write the application state from the object graph to the database.
  • Usually, this happens by syncing the graph to models.

5.4.4 Post-processing

  • Post-processing is what happens before and after data gets persisted. In operations, you have callback objects that can be invoked to run additional logic.

5.4.5 Presentation

  • Presentation is not directly the operation’s responsibility. However, operations can assist rendering a JSON document using a representer, or by providing data to view models(aka Cells).

5.4.6 Authorization

  • Authorization realistically is a task orthogonal to this flow stack and can happen at any point leveraging policy objects.

5.5 Validations

  1. The operation instantiates(實例化) a contract and passes its @model to it.
  2. It is absolutely mission-critical to understand that this step does not touch the model at all. Validation will only operate on the form instance. And this is different to Rails: Validations in ActiveRecord happen on the model itself.
  3. Reform cleanly separates that and embraces the entire validation workflow in the form object.
  4. If the validation was successful without errors, the block passed to #validate is run. This is where you put your business logic for processing the form.
  5. If the input was invalid, the block is not run

5.5.1 Why don't need strong_parameters anymore?

  • I find it important to mention how strong_parameters becomes obsolete(過時的) using operations and forms. The #validate method knows which parameters of the hash go into the form and what is to be ignored.
  • How does it know that? Well, you configured the form’s fields and validations in the ::contract block! The contract just needs to pick the keys it wants from params. Explicitly(明確地) defining structures instead of guessing is a cornerstone of this framework - and will help us a lot in our quest to a cleaner architecture.

6. Cells

6.1 Cells as view model

  • Cells include logic of views
  • Cells and operations can be passed directly into a responder and completely remove the rendering logic from controllers.

7. Concepts

  • In Trailblazer, you structure your app by domain into real components. You implement a concept for every entity in your high-level domain.