ruby on rails
TL;DR: Design-patterns can help keep your Rails code manageable. At Nedap we’ve developed Railjet to help us stick to specific design patterns.
TL; DR:设计模式可以帮助保持Rails代码的可管理性。 在Nedap,我们开发了 Railjet 以帮助我们坚持特定的设计模式。
Almost 8 years ago, I started working on a new application in a language (Ruby) and a framework (Rails) that were new to me. In the beginning, the code looked awesome. The different kinds of logic were spread over not too many files, the files were not too large, and if you looked really well you could see pink unicorns dancing on rainbows in-between the lines of code.
大约8年前,我开始使用对我来说是新的语言( Ruby )和框架( Rails )来开发新的应用程序。 一开始,代码看起来很棒。 各种各样的逻辑分布在太多的文件上,文件也不过大,如果看起来很好,您会看到粉红色的独角兽在代码行之间的彩虹上飞舞。
But unfortunately, it did not take long before cracks became apparent…
但是不幸的是,不久之后裂缝就显现出来了……
滑轨; 好的 (Rails; The good)
For small applications, with little business logic, Rails in its vanilla form works very well. Spreading the code over models, views, and controllers yields a readable application to almost everybody. In this design pattern the models manage the data and contain the logic and rules of the application, the views describe how to represent (parts of) the data, and the controllers accept input and tie models and views together.
对于几乎没有业务逻辑的小型应用程序,原始形式的Rails效果很好。 将代码分布在模型 , 视图和控制器上 ,几乎对每个人都具有可读性。 在这种设计模式下 ,模型管理数据并包含应用程序的逻辑和规则,视图描述如何表示(部分)数据,控制器接受输入并将模型和视图联系在一起。
滑轨; 坏与丑 (Rails; The bad and the ugly)
However, as applications grow larger, they contain more business logic, and more code to represent it. This causes either of three problems in Rails:
但是,随着应用程序的增大,它们包含更多的业务逻辑和更多的代码来表示它。 这会导致Rails中的三个问题之一:
- the controllers get bloated. 控制器变得肿。
- the models get bloated. 模型变得models肿。
- both of the above. 以上两者。
Bloated controllers or models mean there is a lot of code in one place. In my experience this has three main disadvantages:
膨胀的控制器或模型意味着一个地方有很多代码。 以我的经验,这有三个主要缺点:
First and foremost, it makes understanding and maintaining the code harder (and thus more error prone).
首先,这使理解和维护代码更加困难(因此更容易出错)。
Second, with ActiveRecord-models being used in many places in the code, the validation of those models has to be conditional (e.g., a rental car that is currently rented needs a contact person, while if it is the garage it should always have a responsible mechanic). This conditional validation is a major contributor to the ‘bloating’ of the models, but is also complexity in itself.
其次,在代码中的许多地方都使用ActiveRecord-models时,必须对这些模型进行验证(例如,当前租用的租车需要一位联系人,而如果是车库,则应该始终有一个联系人)负责任的机械师)。 此条件验证是模型“膨胀”的主要因素,但其本身也很复杂。
Finally, the same holds for performance optimization. Queries can be optimized for one situation, but less so for the next. Either you add all the optimized, similar-but-slightly-different queries in your model or controller, leading to very bloated models. Or you don’t optimize at all…
最后,性能优化也是如此。 可以针对一种情况优化查询,但针对下一种情况则无法优化。 您可以在模型或控制器中添加所有优化的,相似但略有不同的查询,从而导致模型过大。 否则您根本就不会优化...
Rails does offer an option to deflate the controller and the model using concerns, however those contain the same code, tugged away in a different file.
Rails确实提供了一个使用关注点缩小控制器和模型的选项,但是这些包含相同的代码,被拖到另一个文件中。
Railjet; 为什么? (Railjet; why?)
This is not the first time people have written complex code, nor the first time people saw code get bloated over time. So logically, smart people have come with solutions to this problem already. While working at Nedap Krzysztof Zalewski was inspired by several of those to create Railjet. Specifically, three sets of principles were most influential in the development:
这不是人们第一次编写复杂的代码,也不是人们第一次看到代码随着时间的流逝而膨胀。 因此,从逻辑上讲,聪明的人已经提供了解决此问题的方法。 在Nedap Krzysztof工作期间, Zalewski受其中一些人的启发而创建了Railjet。 具体来说,三组原则在开发中最具影响力:
- the SOLID principles SOLID原则
- Clean Code 清洁代码
- Clean Architecture 清洁建筑
This article is not the place to explain those principles (nor am I the person), but luckily plenty has been written about all three of them. For example, see this Thoughtbot blog for an excellent explanation on SOLID, this gist for a list of core principles behind Clean Code, and this blog-post (by Robert C. Martin, who also coined the term clean code) for the ideas behind clean architecture.
本文不是解释这些原则的地方(我也不是这个人),但是幸运的是,关于这三个原则的文章很多。 例如,请参阅此Thoughtbot博客 ,以获取有关SOLID的出色解释, 要点 ,以获取Clean Code背后的核心原则列表,以及该博客文章 (由Robert C. Martin撰写,他也创造了Clean Code术语)以了解其背后的想法。干净的建筑。
Railjet; 什么? (Railjet; what?)
All of these principles can be implemented by a combination of good conduct and spreading code over POROs (Plain Old Ruby Objects) in addition to the Model-View-Controller in Rails. Those POROs are placed at proper places in your Ruby on Rails repository. Several developers have described how to implement different design patterns in Rails (e.g. here, or here or even maintain gems to help implement them (e.g., Draper). Railjet also is such a gem: it offers a set of conventions on what-should-go-where and some boilerplate code and helpers to make it easier for you.
除了在Rails中使用“模型-视图-控制器”之外,还可以通过在PORO(纯旧Ruby对象)上结合良好的行为和传播代码来实现所有这些原则。 这些PORO放置在Ruby on Rails存储库中的适当位置。 几个开发人员已经描述了如何在Rails中实现不同的设计模式(例如, 在此处或此处 ,甚至维护gem来帮助实现它们(例如Draper )。Railjet也是这样的宝石:它提供了关于应该做什么的一组约定, go-where和一些样板代码和帮助程序,以使您更轻松。
To my knowledge, Railjet offers one of the most extensive sets of design patterns in a single gem. On the app
-directory of your Rails repository you gain (up to) six directories:
据我所知,Railjet在一颗宝石中提供了最广泛的设计模式之一。 在app
您的Rails的-directory存储库您获得(最多)六个目录:
use_cases: This is where the actual business logic goes. One of the core principles of a use case is the single responsibility principle. Use cases are therefore typically small classes, with a
call
method, and possibly a few private methods. Another effect of this core principle is that a use case will often call other use cases, so that its single responsibility stays clear.use_cases :这是实际业务逻辑的去向 。 用例的核心原则之一是单一责任原则 。 因此,用例通常是带有
call
方法的小型类,并且可能还有一些私有方法。 此核心原则的另一个效果是,一个用例通常会调用其他用例,因此其单一职责仍然清晰。repositories: The boundary-with-the-outside-world layer; this is where you gather necessary data (from your DB, files, external API’s) and write-out the results. We often have a repository for each model, returning (collections of) ActiveRecord-models. However, you could have the repositories return plain data objects, making the whole framework agnostic.
仓库 :与外部世界的边界层; 在这里,您可以收集必要的数据(从数据库,文件,外部API)并写出结果。 我们通常为每个模型都有一个存储库,返回(收集)ActiveRecord模型。 但是,您可以让存储库返回纯数据对象,从而使整个框架不可知。
forms: Validate user input. Every attribute can be validated based on (conditional) presence, and datatype. But also, more complex validations (e.g. integers being positive), or completely custom constructed validations can be used.
形式 :验证用户输入。 可以基于(有条件的)存在和数据类型来验证每个属性。 但是,也可以使用更复杂的验证(例如,整数为正)或完全定制构造的验证。
policies: Similar to forms, but for decisions, or validations in the business logic. Policies, in contrast to forms, can directly retrieve data from repositories. And to makes things even better, policies can be combined by composition.
策略 :类似于表单,但用于决策或业务逻辑中的验证。 与表单相比,策略可以直接从存储库检索数据。 为了使事情变得更好,可以按组成组合策略。
presenters: Used to present data generated by the business-logic in versatile way. Typically, presenters implement a
to_json
method to return data to a javascript front-end framework, such as React. But you could also have ato_xml
when used for an API, orto_csv
when writing to a file.演示者 :用于以多种方式展示由业务逻辑生成的数据。 通常,演示者实现
to_json
方法以将数据返回到JavaScript前端框架(例如React)。 但你也可以有一个to_xml
使用的API,或者当to_csv
写入文件时。auth: Used to verify if a user is allowed to perform an action. Normally, the controller would be the place to check if a user is allowed to perform an action, but with complex authorization schemes this means you need a lot of information besides the user.
auth:用于验证是否允许用户执行操作。 通常,控制器将是检查是否允许用户执行操作的地方,但是使用复杂的授权方案,这意味着除了用户之外,您还需要大量信息。
In addtion to to those six design patterns, there is the context
. The context contains data that remains the same during a request, for example the rights of the current user. The context is generated in the controller and passed along when initializing a use case or policy. The context also contains the repository objects, as those could vary depending on the current user.
除了这六个设计模式外,还有context
。 上下文包含在请求期间保持不变的数据,例如当前用户的权限。 上下文在控制器中生成,并在初始化用例或策略时传递。 上下文还包含存储库对象,因为这些对象可能会根据当前用户而有所不同。
轨道喷气机 怎么样? (Railjet; How?)
So how would the flow through a Rails application look like? Here is some pseudocode updating a phone number and sending a confirmation message (happy flow only):
那么通过Rails应用程序的流程将如何? 这是一些伪代码,用于更新电话号码并发送确认消息(仅限快乐流程):
Rails
client
controller received edit call with two parameters:id
andphonenumber
string.Rails
client
控制器接收到带有两个参数的编辑调用:id
和phonenumber
字符串。Controller generates
Client
context for the specific client based on theid
.Controller根据
id
为特定客户端生成Client
上下文。Controller generates
ClientPhone
form object, which validates that thephonenumber
is valid phonenumber.控制器生成
ClientPhone
表单对象,该对象验证phonenumber
为有效电话号码。Controller calls
phonenumber/update
use case, and passes the form along.控制器调用
phonenumber/update
用例,并传递表格。That use case calls:
该用例调用:
-
--
save
on theclient
repository, passing thephonenumber
.save
在client
存储库中,并传递phonenumber
。- Checks the
-检查
AllowPhoneNumberUpdate
auth, which checks if the user is allowed to change thephonenumber
of theclient
.AllowPhoneNumberUpdate
auth,它检查是否允许用户更改client
的phonenumber
。- Calls the
-呼叫
message/send
use case with as arguments theclient
, and a message text.message/send
用例,其中包含client
和一个消息文本作为参数。The
message/send
use casemessage/send
用例- Creates the
-创建
ShouldSendMessage
policy, which checks if the currentclient
is allowed to receive messages.ShouldSendMessage
策略,该策略检查是否允许当前client
接收消息。- Calls
-通话
send
on themessage
repository, which dispatches a message to an external text message service.在
message
存储库上send
,该message
存储库将message
调度到外部文本消息服务。
Railjet is available as a Ruby-gem. The codebase and more information on how to set it up can be found on github.
Railjet可以作为Ruby-gem使用 。 可以在github上找到代码库以及有关如何设置它的更多信息。
轨道喷气机 为什么(如何)不使用它? (Railjet; Why (how) not to use it?)
Railjet should not be a straitjacket. You like the use case pattern, but think the rest is completely ridiculous? Then you can use just the use cases! Or maybe you want to use the repositories in one place, but not the next? No problem, go ahead. Railjet makes it easy to stick to the design pattern, it’s not enforcing them.
Railjet不应成为紧身衣。 您喜欢用例模式,但是认为其余的完全荒谬吗? 然后,您可以只使用用例! 还是您想在一个地方使用存储库,而不是在另一个地方? 没问题,继续。 Railjet可以很容易地坚持设计模式,而不是强制执行它们。
Railjet also isn’t a magic wand. If your application contains an enormous amount of domain logic and complexity, it won’t make that magically go away. Spreading it out over the different patterns, will make it easier to work with and find your way around it. But the complexity is still there, and a (tiny) bit of complexity has even been added in the form of the extra design patterns. The latter also means you should not use Railjet in simple applications. MVC is good enough there, and there is no need to add the complexity of extra design patterns.
Railjet也不是魔杖。 如果您的应用程序包含大量的域逻辑和复杂性,那么它就不会神奇地消失。 将其散布在不同的模式中,将使其更易于使用并找到解决之道。 但是复杂性仍然存在,甚至还以额外的设计模式的形式添加了一点点复杂性。 后者也意味着您不应在简单的应用程序中使用Railjet。 MVC在那里足够好,并且不需要增加额外设计模式的复杂性。
这是完美的! 还是? (It’s perfect! Or is it?)
Don’t be silly. It only makes it easier to stick to a set of principles. And just like any theoretical principle: they often break down once the first bullet gets fired line of code gets written. One thing where we often deviate from the principles is the data that is passed along between use cases.
别傻了 这样做只会更容易遵循一组原则。 就像任何理论原理一样:一旦第一个项目符号被发射,它们通常就会崩溃。 我们经常偏离原则的一件事是用例之间传递的数据。
Ideally, the data would be wrapped in framework agnostic data structures. But as the amount of data grows, and describes more separate (but related) records, managing it becomes more complicated. A lot of those “data management tasks” (e.g.: record relations) have been greatly simplified by ActiveRecord. So we often find ourself deviating from the principle, and passing along AR-objects.
理想情况下,数据将包装在与框架无关的数据结构中。 但是,随着数据量的增长以及描述更多单独(但相关)的记录,对其进行管理变得更加复杂。 ActiveRecord大大简化了许多这些“数据管理任务”(例如:记录关系)。 因此,我们经常发现自己背离了原理,并传递了AR对象。
The use of Context
is another design pattern that makes life, especially debugging-life, difficult sometimes. Use cases need a Context when initialized, which in turn (may) need several objects (e.g.: user
, client
) for initialization. When debugging on the Rails-console, setting this up can become tedious, especially if several different types of context are used in the application. Railjet does offer help in that area by means of a small console helper.
Context
的使用是另一种设计模式,有时会使生活特别是调试生活变得困难。 用例在初始化时需要一个Context,而后者又可能需要多个对象(例如: user
, client
)进行初始化。 在Rails-console上进行调试时,进行设置很麻烦,尤其是在应用程序中使用几种不同类型的上下文时。 Railjet确实通过小型控制台帮助程序在该领域提供了帮助。
Another design pattern which could replace the Context
is DependencyInjection. This would limit the information you need to provide to what is actually needed by the code you're trying to execute. Currently there is no convention or helper-methods to do this in Railjet, but it might be something we add in the future.
可以代替Context
另一种设计模式是DependencyInjection 。 这会将您需要提供的信息限制为您尝试执行的代码实际需要的信息。 目前,在Railjet中尚无惯例或辅助方法,但是将来可能会添加。
Altogether, Railjet helps us stick to design patterns in day to day life. Since the different components are independent and optional, you can refactor existing code in small steps. So, no reason not to start using design patterns yourself today!
总而言之,Railjet帮助我们在日常生活中坚持设计模式。 由于不同的组件是独立的且是可选的,因此您可以一步一步地重构现有代码。 因此,没有理由不今天就开始使用设计模式!
This article was a joint effort by Stephan Roolvink and Djurre de Jong.
翻译自: https://medium.com/nedap/getting-ruby-on-rails-on-track-with-railjet-74bf930a7e9d
ruby on rails