Code Loader Trong Ruby - Hiểu Về Zeitwerk
Với Zeitwerk, bạn có thể streamline lập trình biết rằng class và module có sẵn khắp nơi.
Code Loaders Là Gì?
Code loaders cho phép developer định nghĩa classes và modules qua các file và folder khác nhau và dùng chúng xuyên suốt codebase mà không cần explicitly require chúng. Rails là ví dụ tốt của phần mềm dùng code loaders. Lập trình trong Rails không yêu cầu gọi require tường minh để load model trước khi dùng trong controller. Thực tế, trong Rails 6, mọi thứ trong thư mục app được auto-loaded khi app boot, với vài exception.
Mặc dù dễ nghĩ code loading toàn về việc gọi require, nó không đơn giản vậy. Code loading có thể chia thành ba phần, như sau.
- Auto Loading: Nghĩa là code được load on-the-fly khi cần. Ví dụ, trong Rails, chạy
rails skhông load tất cả model, controller, v.v. Nhưng, khi hit đầu tiên của modelUser, nó chạy cơ chế auto loading để tìm và dùng model. Đây là auto loading đang hoạt động. Điều này có lợi ích cho dev environment, vì ta có faster app vàrails consolestartup times.Rails.config.autoload_pathcontrol path được auto-loaded. - Eager Loading: Nghĩa là code được load vào memory tại app startup và không chờ constant được gọi trước khi require. Trong Rails, code được eager loaded trong production. Từ giải thích trên, autoload code trong production sẽ dẫn đến slow response times, vì mỗi constant sẽ được require on-the-fly.
Rails.config.eager_load_pathscontrol path được eager loaded. - Reloading: Code loader liên tục watch thay đổi file trong
autoload_pathvà reload file khi nhận thấy thay đổi. Trong Rails, điều này khá hữu ích trong development, vì nó cho phép chạyrails svà đồng thời làm thay đổi mà không cần restart rails server. Đây là reloading đang hoạt động.
Chúng ta dễ thấy hầu hết các khái niệm này đã được phát triển và sống trong Rails. Zeitwerk thay đổi điều này! Zeitwerk cho phép chúng ta mang tất cả code loading action đến bất kỳ project Ruby nào.
Zeitwerk Là Gì?
Zeitwerk là code loader hiệu quả và thread-safe cho Ruby và có thể dùng trong bất kỳ project Ruby nào, bao gồm Web frameworks (Rails, Hanami, Sinatra), Cli tools, và gems. Với nó, bạn có thể streamline lập trình biết rằng class và module có sẵn khắp nơi. Truyền thống, Rails và một số gem khác có built-in code loaders để enable chức năng này. Tuy nhiên, Zeitwerk extract các khái niệm này vào gem và cho phép Rubyists áp dụng các khái niệm này cho project của họ.
Cài Đặt Zeitwerk
First things first, chúng ta cần cài gem:
gem install zeitwerk
# HOẶC trong Gemfile
gem 'zeitwerk', '~> 2.4.0'
Cấu Hình Zeitwerk
Vậy hãy bắt đầu với cơ bản:
require 'zeitwerk'
loader = Zeitwerk::Loader.new
...
loader.setup
Code trên instantiate một loader instance và gọi setup. Sau lời gọi setup, loader sẵn sàng load code. Nhưng, trước đó, tất cả cấu hình cần thiết trên loader nên đã được cover. Trong bài này, tôi sẽ cover một vài cấu hình trên loader và convention cho cấu trúc code.
- File Structure: Để Zeitwerk hoạt động, tên file và directory cần match với module và class name chúng định nghĩa. Ví dụ,
lib/my_gem.rb -> MyGem
lib/my_gem/foo.rb -> MyGem::Foo
lib/my_gem/bar_baz.rb -> MyGem::BarBaz
lib/my_gem/woo/zoo.rb -> MyGem::Woo::Zoo
- Root Namespaces: Root Namespaces là thư mục nơi
Zeitwerktìm code của bạn. Khimodulesvàclassesđược reference, Zeitwerk biết tìm root namespaces với matching file name. Ví dụ,
require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.push_dir("app/models")
loader.push_dir("app/controllers")
// match như sau
app/models/user.rb -> User
app/controllers/admin/users_controller.rb -> Admin::UsersController
Có hai cách chính để define root namespace cho hai use case khác nhau. Cách mặc định như dưới:
// init.rb
require 'zeitwerk'
loader = Zeitwerk::Loader.new
loader.push_dir("#{__dir__}/bar")
...
loader.setup
// bar/foo.rb
class Foo; end
Điều này nghĩa là class Foo có thể được reference mà không cần Bar::Foo tường minh, vì thư mục bar hoạt động như root namespace. Cách thứ hai define namespace là explicitly state namespace trong lời gọi push_dir:
// init.rb
require 'zeitwerk'
module Bar
end
loader = Zeitwerk::Loader.new
loader.push_dir("#{__dir__}/src", namespace: Bar)
loader.setup
// src/foo.rb
class Bar::Foo; end
Có vài điều lưu ý từ code này:
- Module
Barđã được định nghĩa trước khi được dùng bởipush_dir. Nếu module chúng ta muốn dùng được định nghĩa bởi third-party, thì một simple require sẽ define nó trước khi chúng ta dùng trong lời gọipush_dir. push_direxplicitly specifies namespaceBar.- File
src/foo.rbđịnh nghĩaBar::Foo, không phảiFoo, và không cần tạo directory, nhưsrc/bar/foo.rb.
Independent code loader: Theo thiết kế, Zeitwerk cho phép mỗi project hoặc app dependency quản lý project tree riêng của nó. Nghĩa là cơ chế code loading của mỗi dependency được quản lý bởi dependency đó. Ví dụ, trong Rails 6, Zeitwerk handle code loading cho Rails app và cho phép mỗi gem dependency quản lý project tree riêng biệt. Là error condition nếu có overlapping file giữa multiple code loaders.
Autoloading: Với setup trên, một khi lời gọi
setupđược thực hiện, tất cả class và module sẽ có sẵn on demand.Reloading: Để enable reloading,
loaderphải được explicitly configured cho nó. Ví dụ,
loader = Zeitwerk::Loader.new
...
loader.enable_reloading # bạn cần opt-in trước setup
loader.setup
...
loader.reload
Lời gọi loader.reload reload project tree on-the-fly, và bất kỳ thay đổi mới nào visible ngay lập tức. Tuy nhiên, chúng ta vẫn cần một surrounding mechanism để detect thay đổi file system và gọi loader.reload. Phiên bản đơn giản như dưới:
require 'filewatcher'
loader = Zeitwerk::Loader.new
...
loader.enable_reloading
loader.setup
...
my_filewatcher = Filewatcher.new('lib/')
Thread.new(my_filewatcher) {|fw| fw.watch {|filename| loader.reload } }
Dùng Zeitwerk Trong Rails
Zeitwerk được enable mặc định trong Rails 6.0. Tuy nhiên, bạn có thể opt-out nó và dùng Rails classic code loader.
# config/application.rb
config.load_defaults "6.0"
config.autoloader = :classic
Dùng Zeitwerk Trong Gems
Zeitwerk cung cấp phương thức tiện lợi cho gems, miễn là chúng dùng standard gem structure (lib/special_gem). Phương thức tiện lợi này có thể dùng như sau:
# lib/special_gem.rb
require 'zeitwerk'
module SpecialGem
end
loader = Zeitwerk::Loader.for_gem
loader.setup
Với standard gem structure, lời gọi for_gem thêm thư mục lib làm root namespace, enable mọi code trong thư mục lib được tìm tự động.
Để thêm inspiration, bạn có thể check out gems dùng Zeitwerk:
- Karafka
- Jets
Tham Khảo
Rails autoloading — how it works, and when it doesn't
Zeitwerk