"Meet rom_rb & dry_rb" by Piotr Solnica

91
MEET ROM-RB & DRY-RB PIOTR SOLNICA

Transcript of "Meet rom_rb & dry_rb" by Piotr Solnica

Page 1: "Meet rom_rb & dry_rb" by Piotr Solnica

MEET ROM-RB & DRY-RBPIOTR SOLNICA

Page 2: "Meet rom_rb & dry_rb" by Piotr Solnica

HI PIVORAK!

I’M SOLNIC :)

Page 3: "Meet rom_rb & dry_rb" by Piotr Solnica

mojotech.com

rom-rb.org dry-rb.org

Page 4: "Meet rom_rb & dry_rb" by Piotr Solnica

THE STORY

Page 5: "Meet rom_rb & dry_rb" by Piotr Solnica

DATAMAPPER 2010

Page 6: "Meet rom_rb & dry_rb" by Piotr Solnica

DATAMAPPER

▸ Active Record ORM with explicit model property definitions

▸ Advanced query API in 2009 already (lazy queries, lazy properties, advanced query operators etc.)

▸ Custom data types for properties

▸ Database-agnostic through adapters

Page 7: "Meet rom_rb & dry_rb" by Piotr Solnica

VIRTUS 2011

Page 8: "Meet rom_rb & dry_rb" by Piotr Solnica

VIRTUS

▸ Typed attribute definitions in PORO extracted from DataMapper

▸ Advanced coercion mechanism

▸ Fully configurable and flexible

▸ Form objects, data objects (ie loaded from JSON), coercion backends etc.

▸ For some reason people loved it…

Page 9: "Meet rom_rb & dry_rb" by Piotr Solnica

DATAMAPPER 2 2012

Page 10: "Meet rom_rb & dry_rb" by Piotr Solnica

DATAMAPPER 2

▸ Active Record => Data Mapper (patterns)

▸ Adapters based on pure relational algebra backend

▸ Advanced Session with Unit of Work for state tracking and data synchronization

▸ Didn’t really work out ¯\_(⊙_ʖ⊙)_/¯

▸ However…

Page 11: "Meet rom_rb & dry_rb" by Piotr Solnica

EXPERIMENTATION BEHIND DM2 HAS BEEN FRUITFUL!

Page 12: "Meet rom_rb & dry_rb" by Piotr Solnica

RESULTS OF THE DATAMAPPER 2 EXPERIMENT

▸ Immutability-oriented object design!

▸ Focus on simpler, foundational abstractions

▸ Embracing small, composable libraries

▸ Lots of interesting new patterns in Ruby

Page 13: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM-RB 2013->2014

Page 14: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM-RB 2013->2014

▸ DataMapper 2 renamed to rom-rb (Ruby Object Mapper) in 2013

▸ More work has been put into its relational algebra backend and optimizing sql queries

▸ Prototype of Session with Unit of Work

▸ Still not production ready ¯\_(⊙_ʖ⊙)_/¯

Page 15: "Meet rom_rb & dry_rb" by Piotr Solnica

( ಠ ಠ)

Page 16: "Meet rom_rb & dry_rb" by Piotr Solnica

“ORM is one of the most complex things that you can ever touch…and we chose it over and over again, without thinking at all, because everybody is doing it”

Rich Hickey

Page 17: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM-RB 2014+

Page 18: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM-RB

Page 19: "Meet rom_rb & dry_rb" by Piotr Solnica

PERSISTENCE & MAPPING TOOLKIT FOR RUBY

Page 20: "Meet rom_rb & dry_rb" by Piotr Solnica

NO COMPLEX & LEAKY ABSTRACTIONS

Page 21: "Meet rom_rb & dry_rb" by Piotr Solnica

REDUCES GLOBAL STATE TO THE MINIMUM

Page 22: "Meet rom_rb & dry_rb" by Piotr Solnica

FIGHTS WITH MUTABLE STATE

Page 23: "Meet rom_rb & dry_rb" by Piotr Solnica

MIXES FP WITH OO

Page 24: "Meet rom_rb & dry_rb" by Piotr Solnica

CORE CONCEPTS

▸ Configuration

▸ Relations

▸ Pipelines

▸ Commands

Page 25: "Meet rom_rb & dry_rb" by Piotr Solnica

CONFIGURATION

Page 26: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'rom'

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

rom = ROM.container(config)

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

Page 27: "Meet rom_rb & dry_rb" by Piotr Solnica

RELATIONS

Page 28: "Meet rom_rb & dry_rb" by Piotr Solnica

class Users < ROM::Relation[:sql] def sorted order(:name) endend

JUST DATA

QUERY LOGIC

rom.relations[:users].sorted# [{:id=>1, :name=>"Jane"}

Page 29: "Meet rom_rb & dry_rb" by Piotr Solnica

RELATIONS ARE CALLABLE

Page 30: "Meet rom_rb & dry_rb" by Piotr Solnica

rom.relations[:users].call# [{:id=>1, :name=>"Jane"}]

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

rom.relations[:users].where(name: 'Joe').call# []

Page 31: "Meet rom_rb & dry_rb" by Piotr Solnica

PIPELINE

Page 32: "Meet rom_rb & dry_rb" by Piotr Solnica

map_names = -> users { users.map { |user| user[:name] } }

user_names = rom.relations[:users] >> map_names

user_names.call# ["Jane"]

PIPELINE OPERATOR

Page 33: "Meet rom_rb & dry_rb" by Piotr Solnica

COMMANDS

Page 34: "Meet rom_rb & dry_rb" by Piotr Solnica

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

create_user = rom.commands[:users][:create]

create_user.call(name: 'Joe')# [{:id=>2, :name=>"Joe"}

Page 35: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM-REPOSITORY

Page 36: "Meet rom_rb & dry_rb" by Piotr Solnica

class UserRepo < ROM::Repository relations :users

def all users.select(:name).to_a endend

Page 37: "Meet rom_rb & dry_rb" by Piotr Solnica

user_repo = UserRepo.new(rom)

user_repo.all# [#<ROM::Struct name="Jane">, #<ROM::Struct name="Joe">]

Page 38: "Meet rom_rb & dry_rb" by Piotr Solnica

UPCOMING COMMAND SUPPORT

Page 39: "Meet rom_rb & dry_rb" by Piotr Solnica

class UserRepo < ROM::Repository[:users] commands :create, update: :by_idend

user = user_repo.create(name: "Jane", email: "[email protected]")

user_repo.update(user.id, name: "Jane Doe")

Page 40: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM 2.0 IS AROUND THE CORNER!!!

Page 41: "Meet rom_rb & dry_rb" by Piotr Solnica

ROM 2.0 + ROM-SQL + ROM-REPOSITORY UPDATES

▸ type-safe and flexible relation schema DSL

▸ association support in schemas

▸ simplified, automatic eager-loading based on association definitions

▸ simplified interface for returning aggregate objects in repositories

▸ support for persisting nested data into multiple relations

Page 42: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-RB

Page 43: "Meet rom_rb & dry_rb" by Piotr Solnica
Page 44: "Meet rom_rb & dry_rb" by Piotr Solnica

‣Low level building blocks ‣High-level abstractions

Page 45: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-CONTAINER

Page 46: "Meet rom_rb & dry_rb" by Piotr Solnica

LIGHTWEIGHT IOC

Page 47: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry-container'

class MyContainer extend Dry::Container::Mixinend

# register a singletonMyContainer.register(:user_repo, UserRepo.new)

# or resolve as a new objectMyContainer.register(:user_repo) { UserRepo.new }

# simply access your registered objectsMyContainer[:user_repo]

Page 48: "Meet rom_rb & dry_rb" by Piotr Solnica

class PersistUser attr_reader :user_repo

def initialize(user_repo) @user_repo = user_repo end

def call(user) user_repo.create(user) endend

PersistUser.new(MyContainer[:user_repo])

Page 49: "Meet rom_rb & dry_rb" by Piotr Solnica

MyContainer.register(:persist_user) do PersistUser.new(MyContainer[:user_repo])end

MyContainer[:persist_user].call(user_data)

Page 50: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-AUTO_INJECT

Page 51: "Meet rom_rb & dry_rb" by Piotr Solnica

CONSTRUCTOR INJECTION MIXIN

Page 52: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry-auto_inject'

Inject = Dry::AutoInject(MyContainer)

Page 53: "Meet rom_rb & dry_rb" by Piotr Solnica

class PersistUser include Inject[:user_repo]

def call(user) user_repo.create(user) endend

Page 54: "Meet rom_rb & dry_rb" by Piotr Solnica

MyContainer.register(:persist_user) do PersistUser.newend

Page 55: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-COMPONENT

Page 56: "Meet rom_rb & dry_rb" by Piotr Solnica

MANAGING STATE

Page 57: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry/component/container'

class Application < Dry::Component::Container configure do |config| config.root = Pathname.new('./my/app') endend

# now you can register a loggerrequire 'logger'Application.register('utils.logger', Logger.new($stdout))

# and access itApplication['utils.logger']

Page 58: "Meet rom_rb & dry_rb" by Piotr Solnica

AUTO-REGISTRATION

Page 59: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry/component/container'

class Application < Dry::Component::Container configure do |config| config.root = '/my/app'

config.auto_register = 'lib' end

load_paths!('lib')end

Page 60: "Meet rom_rb & dry_rb" by Piotr Solnica

# under /my/app/lib/logger.rb we putclass Logger # some neat logger implementationend

# we can finalize the container which triggers auto-registrationApplication.finalize!

# the logger becomes availableApplication[‘logger']

Page 61: "Meet rom_rb & dry_rb" by Piotr Solnica

BOOTING 3RD-PARTY DEPS

Page 62: "Meet rom_rb & dry_rb" by Piotr Solnica

Application.finalize(:persistence) do require '3rd-party/database'

container.register('database') do # some code which initializes this thing endend

Page 63: "Meet rom_rb & dry_rb" by Piotr Solnica

class Application < Dry::Component::Container configure do |config| config.root = '/my/app' endend

# requires needed files and boots your persistence componentApplication.boot!(:persistence)

# and now `database` becomes availableApplication['database']

Page 64: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-TYPES

Page 65: "Meet rom_rb & dry_rb" by Piotr Solnica

A TYPE SYSTEM FOR RUBY WITH COERCIONS AND CONSTRAINTS

Page 66: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-TYPES TURNS RUBY INTO HASKELL

Page 67: "Meet rom_rb & dry_rb" by Piotr Solnica
Page 68: "Meet rom_rb & dry_rb" by Piotr Solnica

NOT REALLY :)

Page 69: "Meet rom_rb & dry_rb" by Piotr Solnica

BUT TYPE-SAFETY MATTERS

Page 70: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry-types'

module Types include Dry::Types.moduleend

Types::Coercible::String[1] # => "1"

Types::Form::Nil[""] # => nilTypes::Form::Float["1.23"] # 1.23

Page 71: "Meet rom_rb & dry_rb" by Piotr Solnica

NilOrString = Types::Strict::Nil | Types::Strict::String

NilOrString[nil] # nilNilOrString["foo"] # "foo"

Page 72: "Meet rom_rb & dry_rb" by Piotr Solnica

DefaultAge = Types::Strict::Int.default(33)

DefaultAge[nil] # 33Default[35] # 35

DefaultAge["35"]# Dry::Types::ConstraintError: "35" violates constraints (type?(Integer) failed)

Page 73: "Meet rom_rb & dry_rb" by Piotr Solnica

UserAge = Types::Strict::Int.constrained(gt: 18)

UserAge[19]# 19

UserAge[18]# Dry::Types::ConstraintError: 17 violates constraints (gt?(18) failed)

Page 74: "Meet rom_rb & dry_rb" by Piotr Solnica

class User < Dry::Types::Struct attribute :id, Types::Strict::Int attribute :name, Types::Strict::Stringend

User.new(id: 1, name: "Jane")

Page 75: "Meet rom_rb & dry_rb" by Piotr Solnica

0.8.0 IS AROUND THE CORNER

Page 76: "Meet rom_rb & dry_rb" by Piotr Solnica

DRY-VALIDATION

Page 77: "Meet rom_rb & dry_rb" by Piotr Solnica

FAST, POWERFUL VALIDATION WITH TYPE-SAFETY & SAFE COERCIONS

Page 78: "Meet rom_rb & dry_rb" by Piotr Solnica

VALIDATION SCHEMAS

Page 79: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry-validation'

UserSchema = Dry::Validation.Schema do required(:login).filled(:str?, size?: 2..64) required(:age).maybe(:int?, gt?: 18)end

Page 80: "Meet rom_rb & dry_rb" by Piotr Solnica

UserSchema.(login: "j", age: 19).messages# {:login=>["length must be within 2 - 64"]}

UserSchema.(login: "jane", age: nil).messages# {}

Page 81: "Meet rom_rb & dry_rb" by Piotr Solnica

SCHEMAS WITH COERCIONS

Page 82: "Meet rom_rb & dry_rb" by Piotr Solnica

require 'dry-validation'

UserSchema = Dry::Validation.Form do required(:login).filled(:str?, size?: 2..64) required(:age).maybe(:int?, gt?: 18)end

Page 83: "Meet rom_rb & dry_rb" by Piotr Solnica

UserSchema.(login: "jane", age: '19').to_h# {:login=>"jane", :age=>19}

UserSchema.(login: "jane", age: '').to_h# {:login=>"jane", :age=>nil}

UserSchema.(login: "j", age: '19').messages# {:login=>["length must be within 2 - 64"]}

UserSchema.(login: "j", age: 'oops').messages# {:age=>["must be an integer", "must be greater than 18"]}

UserSchema.(login: "jane", age: '').messages# {}

Page 84: "Meet rom_rb & dry_rb" by Piotr Solnica

UserSchema.(login: "j", age: 'oops').messages# {:age=>["must be an integer", "must be greater than 18"]}

VALIDATION HINT

VALIDATION ERROR

Page 85: "Meet rom_rb & dry_rb" by Piotr Solnica

0.8.0 IS AROUND THE CORNER

Page 86: "Meet rom_rb & dry_rb" by Piotr Solnica

OMG MORE DRY-RB GEMS!

▸ dry-transaction

▸ dry-monads

▸ dry-initializer

▸ dry-web

▸ dry-web-roda (we plan to add support for more!)

▸ more gems are planned!

Page 89: "Meet rom_rb & dry_rb" by Piotr Solnica

@_solnic_

solnic.eu

Piotr Solnica

Page 90: "Meet rom_rb & dry_rb" by Piotr Solnica

dry-rb.org/resources/reddotrubyconf-2016/

More resources!

Page 91: "Meet rom_rb & dry_rb" by Piotr Solnica

THANK YOU :)