ROM: Ruby Object Mapper. Revolution
Кириллов Александр, Evrone.
1
О чем буду рассказывать?
1. Преимущества и недостатки ActiveRecord
2. Что такое ROM
3. Философия ROM
4. Основные компоненты
5. Больше чем gem
6. Необычные кейсы применения
2
ActiveRecord
Я люблю ActiveRecord,
а вы?
3
ActiveRecord
• Хороший выбор для простой логики предметной области
• Независимость от СУБД
• Встроенная валидация
• Связи (relations)
4
ActiveRecord
...и капля дегтя
• Хороший выбор для простой логики предметной области CRUD
• Независимость от СУБД sql-only?!
• Встроенная валидация Напрямую завязана на модель
• Связи (relations) Магия :)
5
Недостатки ActiveRecord
• Нарушает SRP (Single responsibility principle)
• Нет PORO (Plain Old Ruby Object)
• 1-в-1 маппинг на колонки таблицы
• Вы всегда работаете со строкой таблицы
• Тесты тоже завязаны на базу данных
6
ROM
Ruby Object Mapper
ROM is an open-source persistence and mapping toolkit for Ruby built for
speed and simplicity
“
7
ROM: Концепции и решения
• Минимальная инфраструктура для маппинга и сохранности
• Высокоуровневые абстракции над низкоуровневыми компонентами
• Частные реализации запросов к хранилищу
• Разграничение чтения и изменения данных
• Упрощение базового хранилища (при желании)
• Слабые связи между компонентами
8
ROM: Чтение данных
9
ROM: Изменение данных
10
ROM: Adapter
• свой интефейс для каждой реализации (нет общего или базового)
• внутри себя использует Шлюзы, Наборы данных и Связи
• более высокая абстракция для relations и commands
11
ROM: Repositories
• Отдельный gem: gem install rom-repository
• Инкапсулирует доступ к объектам предметной области
• Автоматически связывает кортежи данных с объектами ROM::Struct
12
ROM: Repositories. Repository Class
class UserRepository < ROM::Repository::Base
relations :users
end
rom = ROM.finalize.env
user_repo = UserRepository.new(rom)
user_repo.users
13
ROM: Repositories. Repository Class
class UserRepository < ROM::Repository::Base
relations :users
def [](id)
users.where(id: id).one!
end
end
user_repo.users[1] # => <ROM::Struct[User] id=1 name="Jane">
14
ROM: Repositories
• ROM::Struct
• доступ к аттрибутам like Hash (по ключам)
• ... или с помощью методов
user = user_repo[1] #=> <ROM::Struct[User] id=1 name="Jane">
user[:id] # 1
user.id # 1
user.to_hash # {:id=>1, :name=>"Jane"}
15
ROM: Relations
• запросы к базам данных для использования в repository
• Представления (view)
• Поддержка составных связей
class UserRepository < ROM::Repository::Base
relations :users, :tasks
def with_tasks(id)
users.by_id(id).combine_children(many: tasks)
end
end
16
ROM: Relations. Auto-Combine
#combine_parents , #combine_children
class TaskRepository < ROM::Repository::Base
relations :users, :tasks, :tags
def with_owner_and_tags(id)
tasks
.by_id(id)
.combine_parents(one: { owner: users })
.combine_children(tags: tags)
end
end 17
ROM: Relations. Data Pipeline (>>)
class Users < ROM::Relation[:memory]
def by_name(name)
restrict(name: name)
end
end
name_list = -> users do
users.map { |user| user[:name] }
end
user_names = rom.relation(:users) >> name_list
rom.relation(:users).to_a # [{ id: 1, name: 'Joe'}]
user_names.to_a # ['Joe'] 18
ROM: Relations. Auto-curry
users_by_name = rom.relation(:users).by_name
# call later on using short `[]` syntax
users_by_name['Jane']
# or
users_by_name.('Jane')
# or more explicit and longer form
users_by_name.call('Jane')
19
ROM: Commands
• Изменение состояния
• ROM::Commands::Create
• ROM::Commands::Update
• ROM::Commands::Delete
• или специфичные для вашего адаптера
20
ROM: Commands
class CreateUser < ROM::Commands::Create[:memory]
relation :users
register_as :create
end
create_user = rom.command(:users).create
new_users = [{name: 'Joe'}, {name: 'Alex'}]
create_user.call(new_users)
21
ROM: Commands
class UpdateUser < ROM::Commands::Update[:memory]
relation :users
register_as :update
result :one
end
# Update user 1, setting `foo` to `"bar"`
rom.command(:users). update . by_id(1) .call(foo: "bar")
22
ROM: Commands.
Составные команды
Результаты одних команд можно предедавать в другие ( >> )
create_tasks = rom.command(:tasks).create
create_user = rom.command(:users).create
new_user = { name: 'Jane' }
new_tasks = [{ title: 'One' }, { title: 'Two' }]
command = create_user.
with(new_user) >> create_tasks.with(new_tasks)
command.call # creates a user, passes to create_tasks
23
ROM: Commands.
Граф комманд
Компановка вызова команд с глубокой вложенностью
user_with_tasks = { user: {id: 1, name: 'Joe', tasks: [
{title: 'Task one'}, {title: 'Task Two'}
]} }
create_user_with_tasks = rom.command([
{ user: :users }, [:create, [:tasks, [:create]]]
])
create_user_with_tasks.call(user_with_tasks) 24
ROM: Mappers
• Приведение объектов бизнес-логики к схебе базы данных
• ... и обратно
• Может использоваться с другими библиотеками
• Основные задачи:
• Переименование и фильтрация, группировка атрибутов
• Приведение типов атрибутов
• Создание агрегатов, иммутабельных объектов и др.
25
ROM: Mappers. Definition
users = ROM.env.relation(:users)
class UserAsEntity < ROM::Mapper
register_as :entity # name of the mapper
relation :users # the name of the relation
model User # the domain model to map tuples to
end
users.as(:entity).to_a
# [ <User @id=1, @name='jane', @email='jane@doo.org'>
# <User @id=2, @name='alex', @email='alex@doo.org'> ]
26
ROM: Mappers. Strategies
• Тонкий интерфейс
• нет знаний о модели предметной области
• самостоятельное инстанцирование
• Обычно - просто Hash
• Толстый интерфейс
• Глубокие знания модели
• Преинициализированные объекты
• Сложный доменный объект со связями и логикой
27
ROM: Mappers. Data Transformation
• Фильтрация атрибутов ( exclude, reject_keys )
• Переименование ( from )
• Оборачивание ( wrap, unwrap )
• Группировка ( group, ungroup )
• Сложение/вычитание fold, unfold
• Комбинирование из разных связей
• Встраивание ( embeded )
28
ROM: Mappers. Пошаговая
трансформация
class UserMapper < ROM::Mapper
relation :users
step do
group :tasks do
...
end
end
step do
... 29
ROM: Forms
class TaskForm < ROM::Model::Form
input do
set_model_name 'Task'
attribute :title, String
end
validations do
relation :tasks
validates :title, presence: true
end
end
30
Что уже есть?
• Релиз 1.0 в конце сентября
• Большое количество адаптеров
• SQL: ado, amalgalite, cubrid, db2, dbi, do, fdbsql, firebird, ibmdb,
informix, jdbc, mysql, mysql2, odbc, openbase, oracle, postgres,
sqlanywhere, sqlite, swift, tinytds
• Graphs: neo4j
• NoSQL: mongo, couchdb; KV: redis
• FileStorage: json, yml, csv; Network: http, git
31
Уже готова интеграция с
Rails, Lotus
32
Нестандартные варианты
использования
• В библиотеках
• Разнообразные источники данных
33
Вопросы?
• Подробнее об SRP:
• Patterns of Enterprise Application Architecture
by Martin Fowler
• ROM:
• http://rom-rb.org
• http://rom-rb.org/tutorials/todo-app-with-rails
• https://github.com/rom-rb
• https://twitter.com/_solnic_
34

Rom - Ruby Object Mapper

  • 1.
    ROM: Ruby ObjectMapper. Revolution Кириллов Александр, Evrone. 1
  • 2.
    О чем будурассказывать? 1. Преимущества и недостатки ActiveRecord 2. Что такое ROM 3. Философия ROM 4. Основные компоненты 5. Больше чем gem 6. Необычные кейсы применения 2
  • 3.
  • 4.
    ActiveRecord • Хороший выбордля простой логики предметной области • Независимость от СУБД • Встроенная валидация • Связи (relations) 4
  • 5.
    ActiveRecord ...и капля дегтя •Хороший выбор для простой логики предметной области CRUD • Независимость от СУБД sql-only?! • Встроенная валидация Напрямую завязана на модель • Связи (relations) Магия :) 5
  • 6.
    Недостатки ActiveRecord • НарушаетSRP (Single responsibility principle) • Нет PORO (Plain Old Ruby Object) • 1-в-1 маппинг на колонки таблицы • Вы всегда работаете со строкой таблицы • Тесты тоже завязаны на базу данных 6
  • 7.
    ROM Ruby Object Mapper ROMis an open-source persistence and mapping toolkit for Ruby built for speed and simplicity “ 7
  • 8.
    ROM: Концепции ирешения • Минимальная инфраструктура для маппинга и сохранности • Высокоуровневые абстракции над низкоуровневыми компонентами • Частные реализации запросов к хранилищу • Разграничение чтения и изменения данных • Упрощение базового хранилища (при желании) • Слабые связи между компонентами 8
  • 9.
  • 10.
  • 11.
    ROM: Adapter • свойинтефейс для каждой реализации (нет общего или базового) • внутри себя использует Шлюзы, Наборы данных и Связи • более высокая абстракция для relations и commands 11
  • 12.
    ROM: Repositories • Отдельныйgem: gem install rom-repository • Инкапсулирует доступ к объектам предметной области • Автоматически связывает кортежи данных с объектами ROM::Struct 12
  • 13.
    ROM: Repositories. RepositoryClass class UserRepository < ROM::Repository::Base relations :users end rom = ROM.finalize.env user_repo = UserRepository.new(rom) user_repo.users 13
  • 14.
    ROM: Repositories. RepositoryClass class UserRepository < ROM::Repository::Base relations :users def [](id) users.where(id: id).one! end end user_repo.users[1] # => <ROM::Struct[User] id=1 name="Jane"> 14
  • 15.
    ROM: Repositories • ROM::Struct •доступ к аттрибутам like Hash (по ключам) • ... или с помощью методов user = user_repo[1] #=> <ROM::Struct[User] id=1 name="Jane"> user[:id] # 1 user.id # 1 user.to_hash # {:id=>1, :name=>"Jane"} 15
  • 16.
    ROM: Relations • запросык базам данных для использования в repository • Представления (view) • Поддержка составных связей class UserRepository < ROM::Repository::Base relations :users, :tasks def with_tasks(id) users.by_id(id).combine_children(many: tasks) end end 16
  • 17.
    ROM: Relations. Auto-Combine #combine_parents, #combine_children class TaskRepository < ROM::Repository::Base relations :users, :tasks, :tags def with_owner_and_tags(id) tasks .by_id(id) .combine_parents(one: { owner: users }) .combine_children(tags: tags) end end 17
  • 18.
    ROM: Relations. DataPipeline (>>) class Users < ROM::Relation[:memory] def by_name(name) restrict(name: name) end end name_list = -> users do users.map { |user| user[:name] } end user_names = rom.relation(:users) >> name_list rom.relation(:users).to_a # [{ id: 1, name: 'Joe'}] user_names.to_a # ['Joe'] 18
  • 19.
    ROM: Relations. Auto-curry users_by_name= rom.relation(:users).by_name # call later on using short `[]` syntax users_by_name['Jane'] # or users_by_name.('Jane') # or more explicit and longer form users_by_name.call('Jane') 19
  • 20.
    ROM: Commands • Изменениесостояния • ROM::Commands::Create • ROM::Commands::Update • ROM::Commands::Delete • или специфичные для вашего адаптера 20
  • 21.
    ROM: Commands class CreateUser< ROM::Commands::Create[:memory] relation :users register_as :create end create_user = rom.command(:users).create new_users = [{name: 'Joe'}, {name: 'Alex'}] create_user.call(new_users) 21
  • 22.
    ROM: Commands class UpdateUser< ROM::Commands::Update[:memory] relation :users register_as :update result :one end # Update user 1, setting `foo` to `"bar"` rom.command(:users). update . by_id(1) .call(foo: "bar") 22
  • 23.
    ROM: Commands. Составные команды Результатыодних команд можно предедавать в другие ( >> ) create_tasks = rom.command(:tasks).create create_user = rom.command(:users).create new_user = { name: 'Jane' } new_tasks = [{ title: 'One' }, { title: 'Two' }] command = create_user. with(new_user) >> create_tasks.with(new_tasks) command.call # creates a user, passes to create_tasks 23
  • 24.
    ROM: Commands. Граф комманд Компановкавызова команд с глубокой вложенностью user_with_tasks = { user: {id: 1, name: 'Joe', tasks: [ {title: 'Task one'}, {title: 'Task Two'} ]} } create_user_with_tasks = rom.command([ { user: :users }, [:create, [:tasks, [:create]]] ]) create_user_with_tasks.call(user_with_tasks) 24
  • 25.
    ROM: Mappers • Приведениеобъектов бизнес-логики к схебе базы данных • ... и обратно • Может использоваться с другими библиотеками • Основные задачи: • Переименование и фильтрация, группировка атрибутов • Приведение типов атрибутов • Создание агрегатов, иммутабельных объектов и др. 25
  • 26.
    ROM: Mappers. Definition users= ROM.env.relation(:users) class UserAsEntity < ROM::Mapper register_as :entity # name of the mapper relation :users # the name of the relation model User # the domain model to map tuples to end users.as(:entity).to_a # [ <User @id=1, @name='jane', @email='jane@doo.org'> # <User @id=2, @name='alex', @email='alex@doo.org'> ] 26
  • 27.
    ROM: Mappers. Strategies •Тонкий интерфейс • нет знаний о модели предметной области • самостоятельное инстанцирование • Обычно - просто Hash • Толстый интерфейс • Глубокие знания модели • Преинициализированные объекты • Сложный доменный объект со связями и логикой 27
  • 28.
    ROM: Mappers. DataTransformation • Фильтрация атрибутов ( exclude, reject_keys ) • Переименование ( from ) • Оборачивание ( wrap, unwrap ) • Группировка ( group, ungroup ) • Сложение/вычитание fold, unfold • Комбинирование из разных связей • Встраивание ( embeded ) 28
  • 29.
    ROM: Mappers. Пошаговая трансформация classUserMapper < ROM::Mapper relation :users step do group :tasks do ... end end step do ... 29
  • 30.
    ROM: Forms class TaskForm< ROM::Model::Form input do set_model_name 'Task' attribute :title, String end validations do relation :tasks validates :title, presence: true end end 30
  • 31.
    Что уже есть? •Релиз 1.0 в конце сентября • Большое количество адаптеров • SQL: ado, amalgalite, cubrid, db2, dbi, do, fdbsql, firebird, ibmdb, informix, jdbc, mysql, mysql2, odbc, openbase, oracle, postgres, sqlanywhere, sqlite, swift, tinytds • Graphs: neo4j • NoSQL: mongo, couchdb; KV: redis • FileStorage: json, yml, csv; Network: http, git 31
  • 32.
  • 33.
    Нестандартные варианты использования • Вбиблиотеках • Разнообразные источники данных 33
  • 34.
    Вопросы? • Подробнее обSRP: • Patterns of Enterprise Application Architecture by Martin Fowler • ROM: • http://rom-rb.org • http://rom-rb.org/tutorials/todo-app-with-rails • https://github.com/rom-rb • https://twitter.com/_solnic_ 34