Почему все Active Record ненавидят?

По мере того, как я узнаю все больше и больше об ООП и начинаю реализовывать различные шаблоны проектирования, я постоянно возвращаюсь к случаям, когда люди ненавидят Active Record .

Часто люди говорят, что он плохо масштабируется (ссылаясь на Twitter в качестве основного примера), но на самом деле никто не объясняет, почему он плохо масштабируется; и / или как достичь плюсов AR без минусов (с помощью похожей, но другой схемы?)

Надеюсь, это не превратится в священную войну по поводу шаблонов проектирования - все, что я хочу знать, это **** конкретно ****, что не так с Active Record.

Если он плохо масштабируется, почему бы и нет?

Какие еще проблемы у него есть?

Ответов (14)

Решение

Есть ActiveRecord Design Pattern и ActiveRecord ORM Library Rails , а также есть множество подделок для .NET и других языков.

Это все разные вещи. В основном они следуют этому шаблону проектирования, но расширяют и модифицируют его множеством разных способов, поэтому, прежде чем кто-либо скажет «ActiveRecord - отстой», его нужно квалифицировать, сказав «какой ActiveRecord, есть куча?»

Я знаком только с ActiveRecord Rails, я постараюсь рассмотреть все жалобы, которые были подняты в связи с его использованием.

@BlaM

Проблема, которую я вижу с Active Records, заключается в том, что это всегда примерно одна таблица

Код:

class Person
    belongs_to :company
end
people = Person.find(:all, :include => :company )

Это генерирует SQL LEFT JOIN companies on companies.id = person.company_id и автоматически генерирует связанные объекты Company, так что вы можете делать это, people.first.company и ему не нужно обращаться к базе данных, потому что данные уже присутствуют.

@ pix0r

Неотъемлемая проблема Active Record заключается в том, что запросы к базе данных автоматически генерируются и выполняются для заполнения объектов и изменения записей базы данных.

Код:

person = Person.find_by_sql("giant complicated sql query")

Это не рекомендуется, поскольку это некрасиво, но в тех случаях, когда вам просто нужно написать необработанный SQL, это легко сделать.

@ Тим Салливан

... и вы выбираете несколько экземпляров модели, вы в основном делаете "выбор * из ..."

Код:

people = Person.find(:all, :select=>'name, id')

При этом будут выбраны только столбцы имени и идентификатора из базы данных, все остальные «атрибуты» в сопоставленных объектах будут просто нулевыми, если вы не перезагрузите этот объект вручную и так далее.

Главное, что я видел в отношении жалоб на Active Record, - это то, что когда вы создаете модель вокруг таблицы и выбираете несколько экземпляров модели, вы в основном выполняете «select * from ...». Это нормально для редактирования записи или отображения записи, но если вы хотите, скажем, отобразить список городов для всех контактов в вашей базе данных, вы можете сделать «выберите город из ...» и получить только города . Для этого с Active Record потребуется выбрать все столбцы, но только с помощью City.

Конечно, разные реализации будут справляться с этим по-разному. Тем не менее, это одна проблема.

Теперь вы можете обойти это, создав новую модель для конкретной задачи, которую вы пытаетесь сделать, но некоторые люди утверждают, что это больше усилий, чем пользы.

Я копаю Active Record. :-)

HTH

Проблема, которую я вижу с Active Records, заключается в том, что это всегда только одна таблица. Это нормально, если вы действительно работаете только с этой таблицей, но когда вы работаете с данными, в большинстве случаев у вас будет какое-то соединение где-то.

Да, соединение обычно хуже, чем отсутствие соединения вообще, когда дело доходит до производительности, но соединение обычно лучше, чем "фальшивое" соединение, если сначала прочитать всю таблицу A, а затем использовать полученную информацию для чтения и фильтрации таблицы B.

Проблема с ActiveRecord заключается в том, что автоматически генерируемые запросы могут вызвать проблемы с производительностью.

В конечном итоге вы выполняете некоторые неинтуитивные трюки для оптимизации запросов, которые заставляют вас задуматься, было бы более эффективным по времени написать запрос вручную.

Я всегда обнаруживал, что ActiveRecord хорош для быстрых приложений на основе CRUD, где Модель относительно плоская (например, не много иерархий классов). Однако для приложений со сложной объектно- ориентированной иерархией DataMapper , вероятно, является лучшим решением. Хотя ActiveRecord предполагает соотношение 1: 1 между вашими таблицами и вашими объектами данных, такого рода отношения становятся громоздкими с более сложными доменами. В своей книге о шаблонах Мартин Фаулер указывает, что ActiveRecord имеет тенденцию выходить из строя в условиях, когда ваша модель довольно сложна, и предлагает DataMapper в качестве альтернативы.

Я убедился, что это правда. В случаях, когда у вас много наследования в вашем домене, сложнее сопоставить наследование с вашей СУБД, чем сопоставить ассоциации или композицию.

Я делаю это так, чтобы иметь "доменные" объекты, к которым ваши контроллеры получают доступ через эти классы DataMapper (или "уровень обслуживания"). Они не являются прямым зеркалом базы данных, но действуют как объектно-ориентированное представление некоторого реального объекта. Предположим, у вас есть класс User в вашем домене, и вам необходимо иметь ссылки или коллекции других объектов, уже загруженные при получении этого объекта User. Данные могут поступать из множества разных таблиц, и шаблон ActiveRecord может сделать это очень сложно.

Вместо прямой загрузки объекта User и доступа к данным с помощью API в стиле ActiveRecord код вашего контроллера извлекает объект User, например, путем вызова API метода UserMapper.getUser (). Именно этот модуль отображения отвечает за загрузку любых связанных объектов из соответствующих таблиц и возврат завершенного объекта «Домен» пользователя вызывающей стороне.

По сути, вы просто добавляете еще один уровень абстракции, чтобы сделать код более управляемым. Содержат ли ваши классы DataMapper необработанный пользовательский SQL, вызовы API уровня абстракции данных или даже доступ к самому шаблону ActiveRecord, на самом деле не имеет значения для кода контроллера, который получает красивый заполненный объект User.

Во всяком случае, вот как я это делаю.

I think there is a likely a very different set of reasons between why people are "hating" on ActiveRecord and what is "wrong" with it.

Что касается ненависти, то есть много яда по отношению ко всему, что связано с Rails. Что касается того, что с этим не так, вполне вероятно, что это похоже на все технологии, и есть ситуации, когда это хороший выбор, и ситуации, когда есть лучший выбор. По моему опыту, ситуация, когда вы не можете воспользоваться преимуществами большинства функций Rails ActiveRecord, - это когда база данных плохо структурирована. Если вы обращаетесь к данным без первичных ключей, с вещами, которые нарушают первую нормальную форму, где для доступа к данным требуется множество хранимых процедур, вам лучше использовать нечто большее, чем просто оболочку SQL. Если ваша база данных относительно хорошо структурирована, ActiveRecord позволяет вам воспользоваться этим.

Чтобы добавить к теме ответа комментаторам, которые говорят, что в ActiveRecord что-то сложно, с помощью реплики фрагмента кода

@Sam McAfee Say you have a User class in your domain, and need to have references to, or collections of other objects, already loaded when you retrieve that User object. The data may be coming from many different tables, and an ActiveRecord pattern can make it really hard.

user = User.find(id, :include => ["posts", "comments"])
first_post = user.posts.first
first_comment = user.comments.first

By using the include option, ActiveRecord lets you override the default lazy-loading behavior.

Попробуйте установить полиморфные отношения «многие ко многим». Не так просто. Особенно, если вы не употребляете ИППП.

My long and late answer, not even complete, but a good explanation WHY I hate this pattern, opinions and even some emotions:

1) short version: Active Record creates a "thin layer" of "strong binding" between the database and the application code. Which solves no logical, no whatever-problems, no problems at all. IMHO it does not provide ANY VALUE, except some syntactic sugar for the programmer (which may then use an "object syntax" to access some data, that exists in a relational database). The effort to create some comfort for the programmers should (IMHO...) better be invested in low level database access tools, e.g. some variations of simple, easy, plain hash_map get_record( string id_value, string table_name, string id_column_name="id" ) and similar methods (of course, the concepts and elegance greatly varies with the language used).

2) long version: In any database-driven projects where I had the "conceptual control" of things, I avoided AR, and it was good. I usually build a layered architecture (you sooner or later do divide your software in layers, at least in medium- to large-sized projects):

A1) the database itself, tables, relations, even some logic if the DBMS allows it (MySQL is also grown-up now)

A2) very often, there is more than a data store: file system (blobs in database are not always a good decision...), legacy systems (imagine yourself "how" they will be accessed, many varieties possible.. but thats not the point...)

B) database access layer (at this level, tool methods, helpers to easily access the data in the database are very welcome, but AR does not provide any value here, except some syntactic sugar)

C) application objects layer: "application objects" sometimes are simple rows of a table in the database, but most times they are compound objects anyway, and have some higher logic attached, so investing time in AR objects at this level is just plainly useless, a waste of precious coders time, because the "real value", the "higher logic" of those objects needs to be implemented on top of the AR objects, anyway - with and without AR! And, for example, why would you want to have an abstraction of "Log entry objects"? App logic code writes them, but should that have the ability to update or delete them? sounds silly, and App::Log("I am a log message") is some magnitudes easier to use than le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert(); . And for example: using a "Log entry object" in the log view in your application will work for 100, 1000 or even 10000 log lines, but sooner or later you will have to optimize - and I bet in most cases, you will just use that small beautiful SQL SELECT statement in your app logic (which totally breaks the AR idea..), instead of wrapping that small statement in rigid fixed AR idea frames with lots of code wrapping and hiding it. The time you wasted with writing and/or building AR code could have been invested in a much more clever interface for reading lists of log-entries (many, many ways, the sky is the limit). Coders should dare to invent new abstractions to realize their application logic that fit the intended application, and not stupidly re-implement silly patterns, that sound good on first sight!

D) the application logic - implements the logic of interacting objects and creating, deleting and listing(!) of application logic objects (NO, those tasks should rarely be anchored in the application logic objects itself: does the sheet of paper on your desk tell you the names and locations of all other sheets in your office? forget "static" methods for listing objects, thats silly, a bad compromise created to make the human way of thinking fit into [some-not-all-AR-framework-like-]AR thinking)

E) the user interface - well, what I will write in the following lines is very, very, very subjective, but in my experience, projects that built on AR often neglected the UI part of an application - time was wasted on creation obscure abstractions. In the end such applications wasted a lot of coders time and feel like applications from coders for coders, tech-inclined inside and outside. The coders feel good (hard work finally done, everything finished and correct, according to the concept on paper...), and the customers "just have to learn that it needs to be like that", because thats "professional".. ok, sorry, I digress ;-)

Well, admittedly, this all is subjective, but its my experience (Ruby on Rails excluded, it may be different, and I have zero practical experience with that approach).

In paid projects, I often heard the demand to start with creating some "active record" objects as a building block for the higher level application logic. In my experience, this conspicuously often was some kind of excuse for that the customer (a software dev company in most cases) did not have a good concept, a big view, an overview of what the product should finally be. Those customers think in rigid frames ("in the project ten years ago it worked well.."), they may flesh out entities, they may define entities relations, they may break down data relations and define basic application logic, but then they stop and hand it over to you, and think thats all you need... they often lack a complete concept of application logic, user interface, usability and so on and so on... they lack the big view and they lack love for the details, and they want you to follow that AR way of things, because.. well, why, it worked in that project years ago, it keeps people busy and silent? I don't know. But the "details" separate the men from the boys, or .. how was the original advertisement slogan ? ;-)

After many years (ten years of active development experience), whenever a customer mentions an "active record pattern", my alarm bell rings. I learned to try to get them back to that essential conceptional phase, let them think twice, try them to show their conceptional weaknesses or just avoid them at all if they are undiscerning (in the end, you know, a customer that does not yet know what it wants, maybe even thinks it knows but doesn't, or tries to externalize concept work to ME for free, costs me many precious hours, days, weeks and months of my time, live is too short ... ).

So, finally: THIS ALL is why I hate that silly "active record pattern", and I do and will avoid it whenever possible.

EDIT: I would even call this a No-Pattern. It does not solve any problem (patterns are not meant to create syntactic sugar). It creates many problems: the root of all its problems (mentioned in many answers here..) is, that it just hides the good old well-developed and powerful SQL behind an interface that is by the patterns definition extremely limited.

This pattern replaces flexibility with syntactic sugar!

Think about it, which problem does AR solve for you?

Мне нравится, как SubSonic делает только одну колонку.
Или

DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)

, или:

Query q = DataBaseTable.CreateQuery()
               .WHERE(DataBaseTable.Columns.ColumnToFilterOn,value);
q.SelectList = DataBaseTable.Columns.ColumnYouWant;
q.Load();

Но Linq по-прежнему остается королем, когда дело доходит до отложенной загрузки.

@BlaM: Иногда я просто реализовал активную запись для результата соединения. Не всегда должно быть отношение Таблица <--> Active Record. Почему не «Результат оператора соединения» <--> Active Record?

Хотя все остальные комментарии относительно оптимизации SQL, безусловно, верны, моя основная претензия к шаблону активной записи заключается в том, что он обычно приводит к несоответствию импеданса . Мне нравится, когда мой домен должен быть чистым и правильно инкапсулированным, что обычно сводит на нет все надежды на использование активного шаблона записи.

Вопрос в шаблоне проектирования Active Record. Не инструмент orm.

Исходный вопрос помечен рельсами и относится к Twitter, который построен на Ruby on Rails. Фреймворк ActiveRecord в Rails - это реализация шаблона проектирования Active Record Фаулера.

Я собираюсь поговорить об Active Record как о шаблоне проектирования, я не видел ROR.

Некоторые разработчики ненавидят Active Record, потому что они читают умные книги о написании чистого и аккуратного кода, и в этих книгах говорится, что активная запись нарушает принцип единой возможности восстановления, нарушает правило DDD, что объект домена должен быть постоянным игнорирующим, и многие другие правила из таких книг .

Во-вторых, объекты домена в Active Record имеют тенденцию быть 1 к 1 с базой данных, что может считаться ограничением в некоторых системах (в основном n-уровневых).

Это просто абстрактные вещи, я не видел реальной реализации этого паттерна в Ruby on Rails.

Некоторые сообщения сбивают меня с толку. Некоторые ответы относятся к «ORM» против «SQL» или что-то в этом роде.

Дело в том, что AR - это просто упрощенный шаблон программирования, в котором вы используете объекты своей предметной области для написания кода доступа к базе данных.

Эти объекты обычно имеют бизнес-атрибуты (свойства компонента) и некоторое поведение (методы, которые обычно работают с этими свойствами).

AR просто говорит «добавить несколько методов к этим объектам домена» для задач, связанных с базой данных.

И я должен сказать, исходя из своего мнения и опыта, что мне не нравится этот узор.

На первый взгляд это может показаться неплохим. Некоторые современные инструменты Java, такие как Spring Roo, используют этот шаблон.

Для меня настоящая проблема связана только с ООП. Шаблон AR заставляет вас каким-то образом добавлять зависимость от вашего объекта к объектам инфраструктуры. Эти объекты инфраструктуры позволяют объекту домена запрашивать базу данных с помощью методов, предложенных AR.

Я всегда говорил, что два уровня являются ключом к успеху проекта. Уровень служб (где находится бизнес-логика или может быть экспортирован с помощью какой-либо технологии удаленного взаимодействия, например, веб-служб) и уровень домена. На мой взгляд, если мы добавим некоторые зависимости (которые на самом деле не нужны) к объектам уровня домена для разрешения шаблона AR, наши объекты домена будет труднее делиться с другими уровнями или (редко) внешними приложениями.

Реализация AR в Spring Roo интересна тем, что не полагается на сам объект, а на некоторые файлы AspectJ. Но если позже вы не захотите работать с Roo и вам придется реорганизовать проект, методы AR будут реализованы непосредственно в объектах вашего домена.

Другая точка зрения. Представьте, что мы не используем реляционную базу данных для хранения наших объектов. Представьте, что приложение хранит объекты нашего домена, например, в базе данных NoSQL или просто в файлах XML. Будем ли мы реализовывать методы, выполняющие эти задачи, в наших доменных объектах? Я так не думаю (например, в случае XM мы добавили бы зависимости, связанные с XML, к нашим объектам домена ... Мне действительно грустно). Почему же тогда мы должны реализовывать методы реляционной БД в объектах предметной области, как говорит шаблон Ar?

Подводя итог, можно сказать, что шаблон AR может звучать проще и удобнее для небольших и простых приложений. Но когда у нас есть сложные и большие приложения, я думаю, что классическая многоуровневая архитектура является лучшим подходом.