1 year with rom on production

32
@gotar | @oskarszrajer gotar.info Oskar Szrajer

Upload: oskar-szrajer

Post on 14-Apr-2017

507 views

Category:

Software


0 download

TRANSCRIPT

@gotar | @oskarszrajer gotar.info

Oskar Szrajer

One year with

on production

What is ROM (Ruby Object Mapper)

https://github.com/orgs/rom-rb

PERSISTENCE & MAPPING TOOLKIT FOR RUBY

http://rom-rb.org

Author of ROM

Piotr Solnica | @_solnic_ | http://solnic.eu

author of ROM, Virtus, Dry-*, Rodakase, ...

Community

• dozen of devs

• very active Gitter channelhttps://gitter.im/rom-rb/chat

• stable API >1.0v

• battle tested on production

History of ROM

• successor of DataMapper - aka DataMapper v2

• first commit - Feb 1 2012

• first version - v0.1 - Jun 15 2013

• first public version - v0.6 - Mar 22, 2015

• v1.0 - stable API - Jan 6, 2016

Core ideas

• reduced global state to minimum

• immutability

• mix OO with FP

• blazing fast

Core concepts

• Configuration

• Relations

• Commands

• Mappers

Configurationrequire 'rom'

config = ROM::Configuration.new( :sql, 'postgres://localhost/rom_repo'

)

rom = ROM.container(config)

rom.gateways[:default] # #<ROM::SQL::Gateway:0x007fde7a087158>

ROM::Configuration.new(

default: [:sql, 'postgres://localhost/db'],

legacy: [:sql, 'mysql://localhost/legacy_db'],

marketing: [:yesql, 'postgres://localhost/db', path: './sql'],

cache: [:redis, 'redis://localhost'],

csv: [:csv, File.expand_path('./data.csv', __FILE__],

events: [:event_store, '127.0.0.1:1113']

# ...

)

Relations

class Users < ROM::Relation[:sql] def by_id(id) where(id: id) end end

rom.relation(:users).by_id(1).to_a # [{:id=>1, :name=>"Jane"}]

Everything is lazy (callable)

rom.relation(:users).by_id(1) #<Db::Relations::Users dataset=#<Sequel::Postgres::Dataset: "SELECT "id", ...

rom.relation(:users).by_id(1).call # [{:id=>1, :name=>"Jane"}]

rom.relation(:users).by_id(1).only(:id).to_a # [{:id=>1}]

rom.relation(:users).by_id(1).only(:id).one # {:id=>1}

Commandsclass Create < ROM::Commands::Create[:sql] relation :users register_as :create

result :one end

create_user = rom.command(:users).create

create_user.call(name: 'Jack') # {id: 2, name: 'Jack'}

class Create < ROM::Commands::Create[:sql] relation :users register_as :create

input NewUserParams validator UserValidator

result :one end

class AuthorWithBooks < ROM::Commands::Create[:sql] #...

end

create_authors_with_books = rom.command(:authors) do |authors| authors.create(:books)

end

create_authors_with_books[authors: evaluated_authors]

# {:id=>1, :code=>"jd001", :name=>"Jane Doe", :email=>"[email protected]"} # {:id=>1, :author_id=>1, :title=>"First", :published_on=>#<Date: 2010-10-10 ((2455480j,0s,0n),+0s,2299161j)>} # {:id=>2, :author_id=>1, :title=>"Second", :published_on=>#<Date: 2011-11-11 ((2455877j,0s,0n),+0s,2299161j)>} # {:id=>3, :author_id=>1, :title=>"Third", :published_on=>#<Date: 2012-12-12 ((2456274j,0s,0n),+0s,2299161j)>}

Mappersclass UsersMapper < ROM::Mapper

relation :users

register_as :entity

attribute :id

attribute :name

attribute :age do

rand(18..90)

end

end

users = rom.relation(:users).as(:entity)

users.call

[

{ id: 1, name: "Jane", age: 22 },

{ id: 2, name: "Jack", age: 64 }

]

Pipelines (everything that responds to #call

can be a mapper)

only_name = -> input { input.map{|u| u[:name]} }

upcase = -> input { input.map{|x| x.upcase} }

users = rom.relation(:users)

result = users >> only_name >> upcase

result.to_a

> ["JANE", "JACK"]

Domain data types & Repositories

class User < Dry::Types::Value attribute :name, Types::Strict::String

end

class UserRepo < ROM::Repository relation :users

def all users.select(:name).as(User).to_a

end end

user_repo = UserRepo.new(rom)

user_repo.all

> [#<User name="Jane">, #<User name="Jack">]

My story

My story

Both project use similar technologies:

• Roda (as HTTP layer)

• ROM

• Yaks (JSON API)

Request cycle

ParamsObject >> Validator >> ROM >>

Mapper(s) >> JSON API Mapper

Mapping & coercion layer

Mapping & coercion layer

combine (or change)

data

convert data to json api format

CQRS

combining dataclass UserRepository < ROM::Repository::Base relations :users, :tasks

def with_tasks(id) users.by_id(id).combine_children(many: tasks) end end

user_repo.with_tasks.to_a

# [#<ROM::Struct[User] id=1 name="Jane" tasks=[#<ROM::Struct[Task] id=2 user_id=1 title="Jane Task">]>, #<ROM::Struct[User] id=2 name="Jack" tasks=[#<ROM::Struct[Task] id=1 user_id=2 title="Jack Task">]>]

pros

• separating persistence from application &

encouraging that boundary

• ability to choose appropriate storage (SQL, NoSQL, ...),

but keeping the same API

• possibility to pull from different data sources,

and combine everything together

pros

• easy way to map (manipulate, coerce) data

• freedom

• community

cons• commands (right now repository do not support them,

force to use very low level API, should be done before v2)

• hard to learn (many new concepts)

• lack of documentation (community will help you)

(currently community works to fix it, and there is always

someone sitting on Gitter to help you)

Worth it?

Q&A@gotar | @oskarszrajer gotar.info

Thx@gotar | @oskarszrajer gotar.info