Rails 应用程序的领域驱动设计:在基本示例中实现服务
Domain Driven Design for Rails App: Implementing a service in a basic example
两个模型:一个 Owner
和一个 Dog
:
owner.rb
class Owner < ActiveRecord::Base
has_one :dog
end
dog.rb
class Dog < ActiveRecord::Base
belongs_to :owner
end
架构如下:
schema.rb
ActiveRecord::Schema.define(version: 123) do
create_table "dogs", force: true do |t|
t.string "name"
t.integer "energy"
t.integer "owner_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "dogs", ["owner_id"], name: "index_dogs_on_owner_id"
create_table "owners", force: true do |t|
t.string "name"
t.string "energy"
t.datetime "created_at"
t.datetime "updated_at"
end
end
非常简单的设置。
我想要一个 owner
带他的 dog
去散步。散步结束后,主人体力下降5,狗体力下降20
很明显,这个 walk_the_dog
action/method,无论它存在于何处,都会影响两个对象:一个 owner
对象和一个 dog
对象(当然还有这个dog 对象恰好与该所有者相关联)。
我不知道把这段代码放在哪里。我知道我 可以 简单地在 owners_controller.rb
中创建一个动作,但这似乎是个坏主意。它看起来像这样:
owners_controller.rb
class OwnersController < ApplicationController
def walk_the_dog
@owner = Owner.find(params[:id])
@owner.energy -= 5
@owner.dog.energy -= 20 # this line in particular seems like bad OO design
@owner.save
@owner.dog.save
end
...
end
据我了解,对象应该只改变自己的状态,不应该改变其他对象的状态。所以这似乎是个坏主意,因为在所有者控制器中,我们不仅要更改 owner
对象的状态,还要更改关联的 dog
对象的状态。
我已阅读有关服务的信息。 walk_the_dog
似乎是服务的绝佳案例,因为据我了解,服务允许对象之间的交互以及多个对象的状态更改。我只是不知道该怎么做 it/implement。
是否应该有一个名为walk_the_dog
的服务对象?它们是否应该只是服务目录中的一个文件,其中包含一堆服务方法——其中一个称为 walk_the_dog
而 owners_controller.rb
只是在是控制器?我不知道下一步是什么。
注意:我看到有人说 "who cares if this breaks OO design. Just do it and if it works, it works." 不幸的是,这不是一个选项。我现在正在开发的应用程序遵循了这种思维方式。应用程序变得非常大,现在维护起来非常困难。我想在应用程序的重大重新设计中解决这种情况。
如果我重构这段代码,我会做以下几件事:
在你的代码中写数字是一件坏事,要么你将它们定义为像 ENERGY_PER_WALK_FOR_DOG = 20
这样的常量,要么更好的方法是在 Dog
的 table 中定义一个字段模型。这样,管理和分配这些值会更好。
add_column :dogs, energy_per_walk, :integer, default: 20
add_column :owners, energy_per_walk, :integer, default: 5
我会在 ApplicationController
class:
中创建一个方法
def walk(resources = [])
resources.each do |resource|
resource.lose_walk_energy # you can refine it more!
end
end
在文件夹app/models/concerns中,我将编写以下模块:
module Walkable
extend ActiveSupport::Concern
# subtract energy_per_walk form the energy saved in db
def lose_walk_energy
self.energy -= self.energy_per_walk
save
end
end
现在,您的方法简化为以下方法:
def walk_the_dog
@owner = Owner.find(params[:id])
walk([@owner, @owner.dog])
end
我会说这应该是Owner模型中的一个方法。您还需要在一个事务中执行这两个操作,以确保两个模型都已更新。
class Owner
has_one :dog
def walk_the_dog
return false if dog.nil?
transaction do
decrement!(:energy, 5)
dog.decrement!(:energy, 20)
end
end
end
两个模型:一个 Owner
和一个 Dog
:
owner.rb
class Owner < ActiveRecord::Base
has_one :dog
end
dog.rb
class Dog < ActiveRecord::Base
belongs_to :owner
end
架构如下:
schema.rb
ActiveRecord::Schema.define(version: 123) do
create_table "dogs", force: true do |t|
t.string "name"
t.integer "energy"
t.integer "owner_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "dogs", ["owner_id"], name: "index_dogs_on_owner_id"
create_table "owners", force: true do |t|
t.string "name"
t.string "energy"
t.datetime "created_at"
t.datetime "updated_at"
end
end
非常简单的设置。
我想要一个 owner
带他的 dog
去散步。散步结束后,主人体力下降5,狗体力下降20
很明显,这个 walk_the_dog
action/method,无论它存在于何处,都会影响两个对象:一个 owner
对象和一个 dog
对象(当然还有这个dog 对象恰好与该所有者相关联)。
我不知道把这段代码放在哪里。我知道我 可以 简单地在 owners_controller.rb
中创建一个动作,但这似乎是个坏主意。它看起来像这样:
owners_controller.rb
class OwnersController < ApplicationController
def walk_the_dog
@owner = Owner.find(params[:id])
@owner.energy -= 5
@owner.dog.energy -= 20 # this line in particular seems like bad OO design
@owner.save
@owner.dog.save
end
...
end
据我了解,对象应该只改变自己的状态,不应该改变其他对象的状态。所以这似乎是个坏主意,因为在所有者控制器中,我们不仅要更改 owner
对象的状态,还要更改关联的 dog
对象的状态。
我已阅读有关服务的信息。 walk_the_dog
似乎是服务的绝佳案例,因为据我了解,服务允许对象之间的交互以及多个对象的状态更改。我只是不知道该怎么做 it/implement。
是否应该有一个名为walk_the_dog
的服务对象?它们是否应该只是服务目录中的一个文件,其中包含一堆服务方法——其中一个称为 walk_the_dog
而 owners_controller.rb
只是在是控制器?我不知道下一步是什么。
注意:我看到有人说 "who cares if this breaks OO design. Just do it and if it works, it works." 不幸的是,这不是一个选项。我现在正在开发的应用程序遵循了这种思维方式。应用程序变得非常大,现在维护起来非常困难。我想在应用程序的重大重新设计中解决这种情况。
如果我重构这段代码,我会做以下几件事:
在你的代码中写数字是一件坏事,要么你将它们定义为像 ENERGY_PER_WALK_FOR_DOG = 20
这样的常量,要么更好的方法是在 Dog
的 table 中定义一个字段模型。这样,管理和分配这些值会更好。
add_column :dogs, energy_per_walk, :integer, default: 20
add_column :owners, energy_per_walk, :integer, default: 5
我会在 ApplicationController
class:
def walk(resources = [])
resources.each do |resource|
resource.lose_walk_energy # you can refine it more!
end
end
在文件夹app/models/concerns中,我将编写以下模块:
module Walkable
extend ActiveSupport::Concern
# subtract energy_per_walk form the energy saved in db
def lose_walk_energy
self.energy -= self.energy_per_walk
save
end
end
现在,您的方法简化为以下方法:
def walk_the_dog
@owner = Owner.find(params[:id])
walk([@owner, @owner.dog])
end
我会说这应该是Owner模型中的一个方法。您还需要在一个事务中执行这两个操作,以确保两个模型都已更新。
class Owner
has_one :dog
def walk_the_dog
return false if dog.nil?
transaction do
decrement!(:energy, 5)
dog.decrement!(:energy, 20)
end
end
end