3

我有一个带有域对象和数据映射器的 Web MVC 应用程序。数据映射器的类方法包含所有数据库查询逻辑。我试图避免镜像任何数据库结构,因此,在构造 sql 语句时实现最大的灵活性。所以,原则上,我尽量不使用任何 ORM 或 ActiveRecord 结构/模式。

让我举个例子:通常,我可以让AbstractDataMapper所有特定的数据映射器类继承一个抽象类——比如UserDataMapper类。然后我可以在 , 中定义一个findById()方法,以通过给定值(例如用户 ID AbstractDataMapper)获取特定表的记录(例如)。但这意味着我总是从单个表中获取记录,而无法使用任何左连接来从与给定用户 ID 对应的其他一些表中获取一些其他详细信息。usersidid

所以,我的问题是:在这些条件下——我自己有义务这样做,我应该实现一个抽象数据映射器类,还是每个数据映射器类都应该包含它自己完全“专有”的数据访问层实现?

我希望我能清楚地表达我的想法。请告诉我,如果我不清楚或者您有任何问题。

非常感谢您的时间和耐心。

4

1 回答 1

4

If I understood your point ...

Having all your concrete mappers inheriting SQL from a common class has several issues that you have missed:

  • parameter names in your domain objects depend on the names of columns
  • there is a "fetching method" in mappers, that don't have a corresponding table
  • you still have configuration (table name), that is expected by superclass
  • the DB schema must have id as name for all of your PRIMARY KEY columns

Now, I'm gonna try to unpack each of those.

Parameter and column names

To create a shared findById() method, the only pragmatic approach is to build it around something like this:

"SELECT * FROM {$this->tableName} WHERE id = :id"

The main issue actually is the wildcard * symbol.

There are two major approaches for populating an entity using a data mapper: use setters or use reflection. In both cases the "names" of a parameters/setters is implied by columns, that you have selected.

In a normal query you can do something like SELECT name AS fullName FROM ..., which lets you to use the query for re-naming the fields. But with a "unified approach", there are no good options.

Each mapper can fetch data by id?

So, the thing is, unless you have a mapper-per-table structure (in which case an active record starts look like pragmatic option), you will end up with few (really common) "edge case" scenarios for your mappers:

  • only used to save data
  • deals with collection and not singular entities
  • aggregates data from multiple tables
  • works with a table, that has composite key
  • it's actually not a table, but an SQL view
  • ... or combination of the above

Your original idea would work just fine in a small scale project (with one or two mappers being an "edge case"). But with a large project, the usage of findById() will be the exception not the norm.

Independent parenting?

To actually get this findById() method in the superclass, you will need a way to communicate the table name to it. Which would mean, that you have something like protected $tableName in you class definition.

You can mitigate it by having abstract function getTableName() in your abstract mapper class, which, when implemented, returns a value of global constant.

But what happens, when your mapper need to work with multiple tables.

It seems like a code smell to me, because information actually crosses two boundaries (for lack of better word). When this code breaks, the error will be shown for SQL in the superclass, which isn't where the error originated from (especially, if you go with constants).

Naming the primary key

This is a bit more controversial opinion :)

As far as I can tell, the practice of calling all primary columns id comes from various ORMs. The penalty, that this incurs, applies only to readability (and code maintenance). Consider these two queries:

SELECT ar.id, ac.id 
  FROM Articles AS ar LEFT JOIN 
       Accounts AS ac ON ac.id = ar.account_id 
 WHERE ar.status = 'published'

SELECT ar.article_id, ac.account_id 
  FROM Articles AS ar LEFT JOIN 
       Accounts AS ac USING(account_id)
 WHERE ar.status = 'published'

As the DB schema grows and the queries become more complex, it gets harder and harder to actually keep track of, what the "id" stands for in what case.

My recommendation would be to try same name for column, when it is a primary as when it is a foreign key (when possible, because in some cases, like for "closure tables, it's not viable). Basically, all columns that store IDs of same type, should have the same name.

As a minor bonus, you get the USING() syntax sugar.

TL;DR

Bad idea. You are basically breaking LSP.

于 2017-07-01T10:34:35.783 回答