Ruby 中的存储库或网关模式

发布于 2025-01-07 13:20:33 字数 732 浏览 1 评论 0原文

如何在 Ruby 中实现存储库或网关模式?

我来自 C# 世界,通常会抽象出我的数据访问,但使用 ActiveRecord 作为 Ruby 中的默认数据访问机制,如何实现这一点并不明显。

我通常在 C# 中做的是使用抽象接口,然后根据具体情况对 EFCustomerRepository、NHibernateCustomerRepository 和 InMemoryCustomerRepository 进行具体实现我注入匹配的具体实现。

那么现在,Ruby 的方式是什么?!

据我了解,在动态语言中,你不需要像 DI(依赖注入)这样的东西。 Ruby 具有强大的语言功能,可以实现 mixin 之类的功能。

但是您会定义 mixin 在类或模块级别静态使用吗?

如果我想针对内存存储库进行开发并在生产中切换到 ActiveRecord-Repository,我该如何编写业务逻辑?

如果这里可能走错了路,因为我习惯用静态类型语言思考。有人会如何用 Ruby 方式解决这个任务?基本上我想让我的持久层变得抽象,并且它的实现可以互换。

编辑:我指的是罗伯特c.马丁斯(unclebob)关于架构的主题演讲

感谢您的帮助...

How can I implement the Repository or Gateway pattern in Ruby?

I come from a C# world and I usually abstract away my data access but with ActiveRecord as the default data access mechanism in Ruby, it's not obvious how to accomplish that.

What I usually would do in C# is work with abstract interfaces and then have a concrete implementation for EFCustomerRepository, NHibernateCustomerRepository and InMemoryCustomerRepository and depending on the situation I inject the matching concrete implementation.

So now, what’s the Ruby way?!

As far as I understand it, in dynamic languages you would not need something like DI (dependency injection).
And Ruby has powerful language features to allow things like mixins.

But you would define the mixin to use statically on class or module-level?

How do I write my business logic if I want to develop against an in-memory repository and in production I would switch to my ActiveRecord-Repository?

If might be on the wrong path here since I'm used to thinking in a statically typed language. How would someone tackle this task the Ruby way? Basically I want to make my persistence layer abstract and it's implementations interchangeable.

EDIT: I am referring to robert c. martins (unclebob) keynote about architecture

Thanks for any help...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

老旧海报 2025-01-14 13:20:33

我明白你在说什么。我也有 .NET 背景。抽象出您的业务逻辑和逻辑在我看来,持久性逻辑是个好主意。我还没有找到适合你的宝石。但您可以轻松地自己制作一些简单的东西。最后,存储库模式基本上是一个委托给持久层的类。

这就是我所做的:

require 'active_support/core_ext/module/attribute_accessors'

class GenericRepository

  def initialize(options = {})
    @scope = options[:scope]
    @association_name = options[:association_name]
  end

  def self.set_model(model, options = {})
    cattr_accessor :model
    self.model = model
  end

  def update(record, attributes)
    check_record_matches(record)
    record.update_attributes!(attributes)
  end

  def save(record)
    check_record_matches(record)
    record.save
  end

  def destroy(record)
    check_record_matches(record)
    record.destroy
  end

  def find_by_id(id)
    scoped_model.find(id)
  end

  def all
    scoped_model.all
  end

  def create(attributes)
    scoped_model.create!(attributes)
  end

private

  def check_record_matches(record)
    raise(ArgumentError, "record model doesn't match the model of the repository") if not record.class == self.model
  end

  def scoped_model
    if @scope
      @scope.send(@association_name)
    else
      self.model
    end
  end

end

然后你可以拥有一个 Post 存储库。

class PostRepository < GenericRepository

  set_model Post

  # override all because we also want to fetch the comments in 1 go.
  def all
    scoped_model.all(:include => :comments)
  end

  def count()
    scoped_model.count
  end

end

只需在控制器中的 before_filter 或初始化或任何地方实例化它即可。在本例中,我将其范围限定为 current_user,以便它仅获取这些记录并仅为当前用户自动创建帖子。

def initialize
  @post_repository = PostRepository.new(:scope => @current_user, :association_name => 'posts')
end

def index
  @posts = @post_repository.all
  respond_with @posts, :status => :ok
end

我遇到了 https://github.com/bkeepers/morphine 这是一个小型 DI 框架。它可能对你有用:)但是 DI 并不是 ruby​​ 中广泛使用的模式。此外,我还实例化了我的存储库,以便将它们的范围限定为当前用户或其他用户。

我正在寻求找到正确的方法来完成您所要求的事情,如果我找到了,我会写一些关于它的文章。但就目前而言,它已经足以在持久性和持久性之间做出明确的划分。我的控制器。如果做得正确,以后切换到不同的系统不会有太大麻烦。或者添加缓存等。

I get what you are saying. I come from a .NET background as well. Abstracting away your business logic & persistance logic is imo a good idea. I haven't found a gem that does it for you yet. But you can easily roll something simple yourself. In the end a repository pattern is basically a class that delegates to your persistance layer.

Here is what I do:

require 'active_support/core_ext/module/attribute_accessors'

class GenericRepository

  def initialize(options = {})
    @scope = options[:scope]
    @association_name = options[:association_name]
  end

  def self.set_model(model, options = {})
    cattr_accessor :model
    self.model = model
  end

  def update(record, attributes)
    check_record_matches(record)
    record.update_attributes!(attributes)
  end

  def save(record)
    check_record_matches(record)
    record.save
  end

  def destroy(record)
    check_record_matches(record)
    record.destroy
  end

  def find_by_id(id)
    scoped_model.find(id)
  end

  def all
    scoped_model.all
  end

  def create(attributes)
    scoped_model.create!(attributes)
  end

private

  def check_record_matches(record)
    raise(ArgumentError, "record model doesn't match the model of the repository") if not record.class == self.model
  end

  def scoped_model
    if @scope
      @scope.send(@association_name)
    else
      self.model
    end
  end

end

And then you could for example have a Post repository.

class PostRepository < GenericRepository

  set_model Post

  # override all because we also want to fetch the comments in 1 go.
  def all
    scoped_model.all(:include => :comments)
  end

  def count()
    scoped_model.count
  end

end

Just instantiate it in your controller in a before_filter or initialize or wherever. In this case I'm scoping it to the current_user so that it only fetches those records and automatically create posts only for the current user.

def initialize
  @post_repository = PostRepository.new(:scope => @current_user, :association_name => 'posts')
end

def index
  @posts = @post_repository.all
  respond_with @posts, :status => :ok
end

I came across https://github.com/bkeepers/morphine which is a tiny DI framework. It could work for you :) But DI isn't a heavily used pattern in ruby. Also, I instantiate my repos in order to scope them to a current user or something else.

I'm on a quest to find the right way to do just what you ask and do a little write-up about it if I ever do find it. But for now it's already sufficient to make the clean cut between persistance & my controllers. If this is done properly it won't be a big hassle to switch to a different system later on. Or add caching etc.

浅笑轻吟梦一曲 2025-01-14 13:20:33

好吧,ActiveRecord 已经提供了抽象持久层 - 它有 几个不同的适配器 允许它使用不同的适配器数据库后端。此外,它是开源的,因此您可以自由地查看它是如何实现的。

乍一看,您会发现它还有一个 AbstractAdapter 所有其他适配器然而,由于 Ruby 是动态的鸭子类型语言,AbstractAdapter 不必包含将在子类中重写的抽象方法,也不必定义他们应该遵守的“契约”。

编辑:

这是一个关于如何在 Ruby 中抽象存储的简单草图,不确定它到底是什么模式:

# say you have an AR model of a person
class Person < ActiveRecord::Base
end

# and in-memory store of persons (simply, a hash)
IN_MEMORY_STORE = {
  :Person => ['Tim', 'Tom', 'Tumb']
}

# this will abstract access
class MyAbstractModel
  def initialize item, adapter
    @item = item
    @adapter = adapter
  end

  # get all elements from the store
  def all
    case @adapter
    when :active_record
      # pull from database:
      Object.const_get(@item).all
    when :in_memory_store
      # get from in-memory store
      IN_MEMORY_STORE[@item]
    else
      raise "Unknown adapter"
    end
  end
end

# get all Persons from in-memory storage...
p MyAbstractModel.new(:Person, :in_memory_store).all
# ...and from a database
p MyAbstractModel.new(:Person, :active_record).all

Well, ActiveRecord already provides abstract persistence layer - it has several different adapters allowing it to use different database backends. Also, it's open-source so you are free to take a look at how it has been achieved.

Upon the first glance you can see that it also has an AbstractAdapter that all other adapters inherit, however, as Ruby is dynamic, duck-typing language, AbstractAdapter doesn't have to contain abstract methods which will be overridden in children classes, neither defines a "contract" that they should honour.

Edit:

Here's a simple sketch on how you could abstract away your storage in Ruby, not sure which pattern exactly it is:

# say you have an AR model of a person
class Person < ActiveRecord::Base
end

# and in-memory store of persons (simply, a hash)
IN_MEMORY_STORE = {
  :Person => ['Tim', 'Tom', 'Tumb']
}

# this will abstract access
class MyAbstractModel
  def initialize item, adapter
    @item = item
    @adapter = adapter
  end

  # get all elements from the store
  def all
    case @adapter
    when :active_record
      # pull from database:
      Object.const_get(@item).all
    when :in_memory_store
      # get from in-memory store
      IN_MEMORY_STORE[@item]
    else
      raise "Unknown adapter"
    end
  end
end

# get all Persons from in-memory storage...
p MyAbstractModel.new(:Person, :in_memory_store).all
# ...and from a database
p MyAbstractModel.new(:Person, :active_record).all
软甜啾 2025-01-14 13:20:33

@serverinfo,我对 C# 不太了解。但当我从 Java/C 背景转向 Ruby 时,当我意识到这种语言到底有多么灵活时,我感到震惊。你说你真正的问题是“抽象出你的持久层并使其可交换”。您还问“我将如何编写业务逻辑”。

我建议您抛弃先入之见,问问自己:“我希望如何在我的业务逻辑层中表达数据访问/存储”?不要担心你认为能做什么或不能做什么;如果您能弄清楚您希望界面如何工作,那么可能有一种方法可以在 Ruby 中完成。

您还必须决定如何指定要使用的具体实现。您是否可能希望对不同的模型对象使用不同的数据存储?您可能想在运行时切换吗?您想在配置文件或代码中指定要使用的后端吗?如果您可以决定想要做什么,Stack Overflow 上有很多人可以帮助您弄清楚如何去做。

@serverinfo, I don't know much about C#. But when I came to Ruby from a Java/C background, I was blown away when I realized how flexible this language really is. You say that your real problem here is to "abstract away your persistence layer and make it exchangeable". You also asked "how will I write the business logic".

I suggest that you throw away your preconceptions and ask yourself: "how would I like to express data access/storage within my business logic layer"? Don't worry about what you think can or can't be done; if you can figure out how you would like the interface to work, there is probably a way it can be done in Ruby.

You will also have to decide how you want to specify the concrete implementation to be used. Is it possible you will want to use a different data store for different model objects? Might you want to switch at run-time? Would you like to specify the backend to be used in a configuration file, or in code? If you can decide what you want to do, there are lots of people on Stack Overflow who can help you figure out how to do it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文