Rails Development Guideline
太长不读
本文档关注 GitLab Rails 后端开发的相关内容,希望能做到以下内容:
- 帮助新人快速了解后端开发的必备知识
- 面对后端需求时,能够快速找到对应代码位置,并且 follow 项目内的最佳实践
- 筛选 GitLab Docs 数量庞大的文档,列出必读精华内容
GitLab Upstream Handbook 中关于极狐的内容
GitLab 整体架构
一定要先了解 GitLab 的整体架构,避免陷入开发细节
- 推荐先看张乾介绍架构的视频,有一个宏观的认识
Meet Google Drive - One place for all your files
- GitLab 文档介绍系统架构和重要组件
[GitLab architecture overview | GitLab](https://docs.gitlab.com/ee/development/architecture.html) |
prepend_mod
利用 Ruby 巧妙的实现了 prepend_mod
方法,正确的使用 prepend_mod 可以在一套代码库管理多个发行版,GitLab 大量使用 prepend_mod ,使得 EE 逻辑覆盖 CE 逻辑,极狐同样使用 prepend_mod 覆盖 GitLab 的逻辑,所以理解 prepend_mod 才能正确的开发
- 张乾和曹宝栋在 RailsConf 的 session,介绍使用 prepend_mod 的场景
Meet Google Drive - One place for all your files
- 曹宝栋 prepend_mod 详细介绍
Devise
GitLab 大量使用了 devise,实现灵活多变的 Authentication 方案,如果要做有关 登录/登出
部分的逻辑。一定要学会 devise,devise 实在太方便了,但同样也太隐晦了,如果不了解的话,真的很难做逻辑修改。
- 官方文档,强烈推荐看
GitHub - heartcombo/devise: Flexible authentication solution for Rails with Warden.
- Devise how-to articles,里面介绍了大量有用的代码案例
How Tos · heartcombo/devise Wiki
Rails Routes
不同于普通 Rails 项目简单的 route 定义,GitLab 有以下不同
- 定义了 routing concern,方便后面复用
Rails Routing from the Outside In - Ruby on Rails Guides
- 在
GitLab::Patch::DrawRoute
定义了draw
方法,用来加载config/routes
,ee/config/routes
文件夹中定义的各种子路由文件,同时极狐也覆盖了draw
方法,加载极狐特有的路由 - 可以在
gitlab
文件夹中运行bundle exec rails routes
列出所有定义的路由,但需要注意,结果列表不包括 Rest API 和 GraphQL API
REST API
GitLab 使用 Grape Gem
来定义 OpenAPI,一方面提供给 public 使用,另一方面也会在组件之间使用,比如 gitlab runner 通过 API 上传 job log,或者 GitLab 前端调用
- REST API 定义在
config/routes/api.rb
, 通过mount ::API::API => '/'
加载lib/api/api.rb
, 并在API::API
中给所有 API path 定义加载/api
作为 prefix - grape 官方文档
GitHub - ruby-grape/grape: An opinionated framework for creating REST-like APIs in Ruby.
- 现存 REST API Authentication 方式
[API Docs | GitLab](https://docs.gitlab.com/ee/api/#authentication) |
- 现存 Rest API 列表,但不是特别全,极狐可以考虑修改 upstream 的 api 定义文件,贡献一波
[REST API resources | GitLab](https://docs.gitlab.com/ee/api/api_resources.html) |
GraphQL API
与 REST API 类似,GraphQL API 也可以对外提供 API 能力
- Graphql 中文文档,可以快速了解相关知识
- GraphQL 入口在
config/routes/api.rb
, 通过match '/api/graphql', via: [:get, :post], to: 'graphql#execute'
定义路由。同时你也可以通过访问127.0.0.1:3000/-/graphql-explorer
或者https://gitlab.com/-/graphql-explorer
进入 GraphQL 调试页面 - GitLab 使用 GraphQL 帮助文档
[Get started with GitLab GraphQL API | GitLab](https://docs.gitlab.com/ee/api/graphql/getting_started.html) |
- GitLab GraphQL APL list,可以查看目前定义的所有 GraphQL endpoint
[GraphQL API Resources | GitLab](https://docs.gitlab.com/ee/api/graphql/reference/) |
Rails Controller
- 需要理解 prepend_mod,因为极狐经常需要覆盖 upstream 的 controller 逻辑
- 不要写过多的逻辑在 Controller 层,从现在 GitLab 的做法看,重要的逻辑都在 Model 或者 Service 中
- upstream 已经不推荐写 controller 测试了,推荐写 requests 测试,具体看下面的MR
Transition from controller specs to request specs (&5076) · Epics · GitLab.org
- 修改 controller 代码时,最烦人的就是被藏在各处的 before_action 干扰,这时可以在 rails console 中通过命令
XXXController.new._process_action_callbacks.map { |c| c.filter if c.kind == :before }
列出某一个 XXXController 有多少个 before_action,方便我们查看。或者通过在 controller 文件的最底部添加下面的代码,这样在运行测试时,某一个 action 的before_action 运行之前都会运行binding.pry
,方便大家调试
class XXXController
...
_process_action_callbacks.each do |callback|
define_method(callback.filter.to_s) do |*args|
puts "Running before_action: #{callback.filter}"
require'pry'
binding.pry
super(*args)
end if callback.kind == :before
end
end
请参考下面的文章
Debugging Action Callbacks (aka Filters) in Rails
Rails Model
具体内容待总结
- 刘文举同学分享的 user, group 的视频
Meet Google Drive - One place for all your files
- zhu shuang同学分享的 project 的视频
Meet Google Drive - One place for all your files
- 邓加红同学分享 issue 的视频
Meet Google Drive - One place for all your files
Meet Google Drive - One place for all your files
Rails Service
待总结
Rails Lib
待总结
Rails Initializer:
- GitLab 有非常多的 initializer,目前通过文件名最前面的数字前缀保证加载顺序
- 在
application.rb
中,通过initializer :load_jh_config_initializers, after: :load_config_initializers
确保极狐初始化文件在 upstream 之后加载
DB Migration:
目前极狐需要做任何数据库的修改,都需要在 upstream repo 上面进行修改,而且还需要注意以下几点:
- 需要区分 migration 的类型,目前分成 Regular Schema Migrations,Post-deployment Migration,Background Migrations
[Migration Style Guide | GitLab](https://docs.gitlab.com/ee/development/migration_style_guide.html#choose-an-appropriate-migration-type) |
- Migration 文件开发指南
[Migration Style Guide | GitLab](https://docs.gitlab.com/ee/development/migration_style_guide.html#schema-changes) |
- 所有的 migration 都需要写测试
[Testing Rails migrations at GitLab | GitLab](https://docs.gitlab.com/ee/development/testing_guide/testing_migrations_guide.html#testing-rails-migrations-at-gitlab) |
- 注意 migration 的运行时间,regular migrations 要小于 3分钟,post-deployment migrations 小于 10分钟,background migrations 大于 10分钟
- pipeline 中 check-migrations 会对比 db/structure.sql 结构,如果 fail 记得 rebase 最新 master 分支
Rails View
在 repo 中一共有使用 3 个 application_helper.rb
,分别保证将 CE,EE 和 JH 文件夹添加进入 ActionView::LookupContext,这样 CE,EE 和 JH 的 controller 中可以定位到对应的 view 文件
Feature Flag
Feature Flag 是一种编程技巧,通过它可以让我们在程序运行时动态打开或关闭某些功能
- 目前 GitLab 推荐,根据下面的场景,在开发前判断是否需要 feature flag
Feature flags in development of GitLab
- Feature Flag的生命周期大概是
- 需要先添加 feature flag,但要保证现有功能不变
- 适当时候打开 feature flag,验证新功能的可靠性
- 最后删除 feature flag
[Feature flags in the development of GitLab | GitLab](https://docs.gitlab.com/ee/development/feature_flags/#feature-flags-in-gitlab-development) |
- 目前 GitLab 的 Feature Flag 存在三种,
development type(short-lived feature flag)
,用于开发,ops type(long-lived)
, 用于运维,experiment type
, 用于 A/B testing
# 默认type就是development
Feature.enable(:<dev_flag_name>) Feature.disable(:<dev_flag_name>)
# 第二个参数叫做 actor, 目前只接受 User, Project 和 Group 三种
Feature.enable(:<dev_flag_name>, Project.find(<project id>))
# 检查 Feature flag 是否打开
Feature.enabled?(:dev_flag_name) Feature.disabled?(:dev_flag_name)
# 检查 ops feature flag是否打开
Feature.enabled?(:my_ops_flag, type: :ops)
# 如果要强制从yaml文件中读取默认值,则使用 default_enabled: :yaml
Feature.enabled?(:feature_flag, project, default_enabled: :yaml)
#上面的方法实现在 lib/feature.rb 中
- 在运行非 End to End 测试时,通过在
spec_helper.rb
中设置,几乎打开了所有的 development feature flag
config.before(:suite) do
stub_all_feature_flags #开启feature flag
# 上面的方法实现在 spec/support/helpers/stub_feature_flags.rb
end
config.before do |example|
# 除非测试加上了 stub_feature_flags: false 的 tag,否则只关闭了特定的 flag
if example.metadata.fetch(:stub_feature_flags, true)
...
else
end
- 对于 End to end 测试,upstream 推荐使用 public api 打开或关闭某些 feature flag 达到测试的目的
[Feature flags in the development of GitLab | GitLab](https://docs.gitlab.com/ee/development/feature_flags/#end-to-end-qa-tests) |
Testing
GitLab 非常重视测试,强烈推荐阅读下面的文档
[Testing levels | GitLab](https://docs.gitlab.com/ee/development/testing_guide/testing_levels.html#about-controller-tests) |
- 目前有 4 类测试
- 单元测试,比如 model 测试或者 service 测试,数量最多,覆盖最全
- 集成测试,一般指 controller 或者 request 测试,目前 upstream 推荐写 request 测试,不在新写 controller 测试
- 白盒功能测试,放在 spec/features 文件夹下
- 黑盒功能测试,放在 qa/ 文件夹下
-
我推荐的测试策略,这部分大家可以一起讨论
- 写自动化测试就像投资,写测试花费的时间就是成本,节省下手工测试的时间和验证功能的重要程度就是收益,既然是投资,那就要追求最大投资回报率。也就是说写出来的测试,验证的功能越重要,验证的次数越多,回报率越高。对于不同类型的测试,“投资策略”也不一样
- 单元测试:dev 一定要写,测试成本低,覆盖全分支
- 集成测试:dev 一定要写,测试成本低,相对可以少覆盖一些分支
- 白盒功能测试:dev 或者 QA 写
- 由 QA 给出
重要功能列表
,写测试覆盖列表中所有的功能 - 对于在迭代中碰到需求,要
新加/修改功能
,团队成员决定是否写对应测试,如果多数人觉得没必要写,可以不写
- 由 QA 给出
- 黑盒功能测试:QA 写
- 黑盒测试更偏向于验证各种线上环境的用户流程,而非某个特定功能,所以可以精简
重要功能列表
,然后写测试将功能串起来 - 有些功能或场景在产品环境很难自动化验证,比如发送短信验证码(因为要过人机识别),可以考虑使用别的方法验证功能,比如监控发送短信的成功率,创建定期任务检查短信余额等。换句话说,写这类测试成本太高,有点亏本,可以考虑更换投资方式。
- 黑盒测试更偏向于验证各种线上环境的用户流程,而非某个特定功能,所以可以精简
- 极狐有时候会覆盖 upstream 的某些逻辑,导致 upstream 测试失败,经与 upstream 团队讨论过后,初步的方案是
- 优先考虑通过修改 assertion 来使 upstream 测试通过
- 如果极狐修改了流程,比如注册时添加手机号验证,而导致大量 upstream 测试失败,可以通过在运行 upstream 测试时关闭相关功能通过测试
- 如果失败的 upstream 测试使用了国内无法访问的资源,比如 google 服务等,直接跳过即可
- 请尽可能少的跳过测试
- 测试跳过配置在:
jh/spec/skip_specs/skip_list.yml
-
skip_list.yml
配置文件通过文件分组,description
可以通过执行测试失败信息里取到 - 请在新增时,通过注释加上原因以及 Issue/MR 链接,方便追溯原因
I18N
GitLab 支持多国语言,开发人员可以直接在代码中使用 I18N 相关的方法完成对 I18N 的支持,具体开发流程如下:
- 在代码中调用 I18N 的相关方法,比如
s__('Namespace|Label')
- 运行
bundle exec rake gettext:regenerate
, 自动扫描代码中所有需要翻译的文本,更新到locale/gitlab.pot
文件中 - 提交
locale/gitlab.pot
文件的修改到代码库中,程序员的工作基本上就完成了 - 后续翻译工作,GitLab 使用
Crowdin
系统进行
- GitLab I18N 介绍
[Internationalization for GitLab | GitLab](https://docs.gitlab.com/ee/development/i18n/externalization.html#always-pass-string-literals-to-the-translation-helpers) |
- GitLab I18N 在代码库中的运作原理
Meet Google Drive - One place for all your files
Sidekiq
GitLab 使用 Sidekiq 作为后台 worker,异步运行一些任务,比如发送邮件,清理数据之类的
- GitLab Sidekiq Development
[Sidekiq guides | GitLab](https://docs.gitlab.com/ee/development/sidekiq/#sidekiq-queues) |
具体内容待总结
Application Setting & gitlab.yml
GitLab 有非常非常多的配置项,目前存放在两个地方,一个是 gitlab.yml ,另一个是 Application Setting Model。在 gitlab.yml 中有注释说新配置都放在 Application Setting 中
,所以可以推断出,GitLab 先把配置项放在 yaml 文件中,然后又放到了数据库 application_setting 表中。
- 代码库中使用
settingslogic gem
方便的读取 gitlab.yml,比如Settings.db.username
就在读取 yaml 中db → username
的相关配置 - Application Setting 是一个 Active Record Model,有大约 100 个字段保存着各种各样的配置,同时完成发布之后的 Feature Toggle,如果是全局开关,理论上也应该变成 Application Setting 的一个字段
- 如果开发过程中需要对本地配置进行修改,需要明确相关配置是在 yaml 中还是数据库中。另外,也可以在 gitlab-development-kit 下对 gdk.yml 进行修改,然后 gdk reconfigure 修改 gitlab 的相关配置
- 遗留问题:没有找到 self manage GitLab 如何修改 Application Setting 内容的相关方法或者底层原理,期待有人解答:)
GitLab Integration
具体内容待总结