pofeaa and sqlalchemy

80
PofEAA and SQLAlchemy INADA Naoki @methane

Upload: inada-naoki

Post on 27-Jan-2015

111 views

Category:

Technology


5 download

DESCRIPTION

 

TRANSCRIPT

Page 1: PofEAA and SQLAlchemy

PofEAA andSQLAlchemy

INADA Naoki@methane

Page 2: PofEAA and SQLAlchemy
Page 3: PofEAA and SQLAlchemy

Public Activity

MessagePack-PythonJSON like binary protocol

wsaccelWebSocket accelerator for Tornado, ws4py

MyKazePyMySQL in Tornado (WIP)

Python for PHPerQuick guide of Python for PHPer (Japanese)

Page 4: PofEAA and SQLAlchemy

Online Game Developer

R&D and ArchitectTuning server side middleware and application

KLab Inc.

Page 5: PofEAA and SQLAlchemy

SQLAlchemy

Page 6: PofEAA and SQLAlchemy

Great O/R Mapperfor Python

Page 7: PofEAA and SQLAlchemy

Patterns ofEnterprise Application

Architecture

Page 8: PofEAA and SQLAlchemy

Design patternsgood to know

for Web Programmers

Page 9: PofEAA and SQLAlchemy

SQLAlchemyQuickly

(from tutorial)

Page 10: PofEAA and SQLAlchemy

● Dialects -- Various DB-API wrapper

● Engine -- Connection management

● Schema and Types

● SQL expression

● and O/R Mapper

SQLAlchemy features

Page 11: PofEAA and SQLAlchemy

Create Enginefrom sqlalchemy import create_engine

engine = create_engine('sqlite:///:memory:',

echo=True)

# Using engine without O/R mapper

con = engine.connect()

with con.begin() as trx:

con.execute('SELECT 1+1')

con.close()

Page 12: PofEAA and SQLAlchemy

Define Object and Schemafrom sqlalchemy.ext.declarative import declarative_base

from sqlalchemy import Column, Integer, String

Base = declarative_base()

class User(Base):

__tablename__ = 'users'

id = Column(Integer, primary_key=True)

name = Column(String)

email = Column(String, nullable=False, unique=True)

password = Column(String, nullable=False)

Page 13: PofEAA and SQLAlchemy

Create table on DB

Base.metadata.create_all(engine)

Output:CREATE TABLE users (

id INTEGER NOT NULL,

name VARCHAR,

email VARCHAR NOT NULL,

password VARCHAR NOT NULL,

PRIMARY KEY (id),

UNIQUE (email)

)

Page 14: PofEAA and SQLAlchemy

Create an instance>>> ed_user = User(name='ed',

... email='[email protected]',

... password='edspassword')

...

>>> ed_user.password

'edspassword'

>>> str(ed_user.id)

'None'

Page 15: PofEAA and SQLAlchemy

Save itfrom sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)

session = Session()

session.add(ed_user)

session.commit()

Page 16: PofEAA and SQLAlchemy

Too complicated?

Page 17: PofEAA and SQLAlchemy

Why not just

engine.save(user)

What's session?

Page 18: PofEAA and SQLAlchemy

Session is Unit of Work

Page 19: PofEAA and SQLAlchemy

Unit of Work isa Pattern

in PofEAA

Page 20: PofEAA and SQLAlchemy

Part 1. The Narratives3. Mapping to RDB

Part 2. The Patterns10. Data Source Architectural Patterns11. O/R Behavioral Patterns12. O/R Structural Patterns13. O/R Metadata Mapping Patterns

O/R chapters in the P of EAA

Page 21: PofEAA and SQLAlchemy

Part 1. The Narratives3. Mapping to RDB

Part 2. The Patterns10. Data Source Architectural Patterns11. O/R Behavioral Patterns12. O/R Structural Patterns13. O/R Metadata Mapping Patterns

O/R chapters in the P of EAA

Page 22: PofEAA and SQLAlchemy

11. O/R Behavioral Patterns

● Unit of Work

● Identity map

● Lazy load

Page 23: PofEAA and SQLAlchemy

Unit of Work

Target: Maintains set of objects to save in transaction.

How:1. Wrap transaction.2. Keep track of new, modified and deleted objects.3. Save objects when commit

Page 24: PofEAA and SQLAlchemy

Why Unit of Work

Remove boilerplate saving code from domain logic.

Avoid objects saved too often.

Page 25: PofEAA and SQLAlchemy

Unit of Work in SQLAlchemy# Create an Unit of Work

session = Session()

# Get connection and start transaction

ed_user = session.query(User).\

filter(User.name=='ed').one()

ed_user.name='E.D.'

session.commit() # save ed_user and commit

Page 26: PofEAA and SQLAlchemy

Object states

user = User(...)

# Transient (not saved)

session.add(user)

# pending (saved)

session.flush()

# persistent (saved if modified)

session.expunge(user)

# Detached (not saved)

Page 27: PofEAA and SQLAlchemy

Flushing

Execute insert / update /delete query.

Session flushes automatically when queryingto avoid inconsistent result. (autoflush)

You can stop autoflush.You can manually flush.

Page 28: PofEAA and SQLAlchemy

expire -- cause reload

session.expire(user)print(user.name) # Cause reloading

commit() # expires all objects for consistency.

You can avoid this by expire_on_commit=False.

Page 29: PofEAA and SQLAlchemy

Contextual Sessionfrom sqlalchemy.orm import (sessionmaker,

scoped_session)

Session = scoped_session(

sessionmaker(bind=engine))

Session.add(ed_user)

Session.commit()

...

Session.remove()

Page 30: PofEAA and SQLAlchemy

11. O/R Behavioral Patterns

● Unit of Work

● Identity map

● Lazy load

Page 31: PofEAA and SQLAlchemy

Target:Avoid two objects for one record.

How:Keep mapping of (table, pk) -> object.Check the map when querying.

Bonus: It acts like a easy cache

Identity map

Page 32: PofEAA and SQLAlchemy

Identity map in SQLAlchemy

Session has identity map.

# Looks up id map after queryingsession.query(User).filter(User.id==3).one()

# Looks up id map before and after querying.session.query(User).get(3)

NOTE: Id map is weak reference by default.

Page 33: PofEAA and SQLAlchemy

11. O/R Behavioral Patterns

● Unit of Work

● Identity map

● Lazy load

Page 34: PofEAA and SQLAlchemy

Lazy Load

Load relation or heavy column on demand.

Page 35: PofEAA and SQLAlchemy

Lazy loading relationship

http://docs.sqlalchemy.org/en/rel_0_8/orm/loading.html

SQLAlchemy uses lazy loading by default for relationship.

You can choose eager loading strategy.

Page 37: PofEAA and SQLAlchemy

O/R Structural Pattern

Page 38: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 39: PofEAA and SQLAlchemy

Object have PK in database as a field.

class Player(Base):

id = Column(INTEGER, primary=True)

Identity Field

Page 40: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 41: PofEAA and SQLAlchemy

Map one to one or one to many relation toObject reference.

Not:session.query(Address).get(user.address_id)

Yes:user.address

Foreign Key Mapping

Page 42: PofEAA and SQLAlchemy

With propertyfrom werkzeug.utils.cached_property

class User(Base):

...

@cached_property

def address(self):

session.query(Address).\

get(self.address_id)

This is enough to achieve ``user.address``.

But no SQLA support.

Page 43: PofEAA and SQLAlchemy

relationshipclass User(Base):

...

addresses = relationship('Address',

backref='user')

class Address(Base):

...

user_id = Column(Integer,

ForeignKey('user.id'))

Page 44: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 45: PofEAA and SQLAlchemy

Association Table Mapping

Mapping many-to-many association table toobject reference.

In SQLAlchemy:Pass “secondary” argument to relationship()http://docs.sqlalchemy.org/en/rel_0_8/orm/tutorial.html#building-a-many-to-many-relationship

Page 46: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 47: PofEAA and SQLAlchemy

Dependent Mapping

O/R map ownership without identity.

PofEAA says:

I don’t recommend Dependent Mapping if you’re using Unit of Work.

Page 48: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 49: PofEAA and SQLAlchemy

Embedded Value

Map an object to some columns in a row.

class Duration: start_date end_date

class Account: duration

CREATE TABLE account ( … start_date DATE, end_date DATE, …);

Page 50: PofEAA and SQLAlchemy

Embedded Value by property@property

def duration(self):

return Duration(

self.start_date, self.end_date)

@duration.setter

def duration(self, duration):

self.start_date = duration.start

self.end_date = duration.end

Page 51: PofEAA and SQLAlchemy

Embedded Value by Compositehttp://docs.sqlalchemy.org/en/rel_0_8/orm/mapper_config.html#mapper-composite

class Account(Base):

...

start_date = Column(DATE)

end_date = Column(DATE)

duration = composite(Duration, start_date, end_date)

class Duration(namedtuple(‘Duration’, ‘start end’)):

def __composite_value__(self):

return self

Page 52: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 53: PofEAA and SQLAlchemy

Serialized LOB

Serialize objects and save to XLOB column.

Page 54: PofEAA and SQLAlchemy

Using property

_info = Column('info', BLOB)

@property

def info(self):

return json.loads(self._info)

@info.setter

def _set_info(self, info):

self._info = json.dumps(self)

Page 55: PofEAA and SQLAlchemy

Custom Type

SQLAlchemy provides PickleType for serialized LOB.

You can define custom type via TypeDecorator:http://docs.sqlalchemy.org/en/rel_0_8/core/types.html#marshal-json-strings

Page 56: PofEAA and SQLAlchemy

12. O/R Structural Pattern

● Identity Field● Foreign Key Mapping● Association Table Mapping● Dependent Mapping● Embedded Value● Serialized LOB● Single Table Inheritance● Class Table Inheritance● Concrete Table Inheritance● Inheritance Mappers

Page 57: PofEAA and SQLAlchemy

Single Table Inheritance

Player

SoccerPlayer BaseballPlayer

CREATE TABLE player( id INTEGER PRIMARY KEY, type INTEGER NOT NULL, position INTEGER,)

All classes saved into one table.

Page 58: PofEAA and SQLAlchemy

Single Table Inheritance in SQLAclass Player(Base):

__tablename__ = ‘player’

id = Column(INTEGER, primary_key=True)

position = Column(INTEGER)

type = Column(INTEGER, nullable=False)

__mapper_args__ = {‘polymorphic_on’: type}

SOCCER_PLAYER = 1

BASEBALL_PLAYER = 2

class SoccerPlayer(Player):

__mapper_args__ = {

‘polymorhic_identity’: Player.SOCCER_PLAYER}

Page 60: PofEAA and SQLAlchemy

Class Table Inheritance

Player

SoccerPlayer BaseballPlayer

CREATE TABLE player( id INTEGER PRIMARY KEY, type INTEGER NOT NULL)CREATE TABLE soccer_player( id INTEGER PRIMARY KEY, position INTEGER NOT NULL)CREATE TABLE baseball_player( id INTEGER PRIMARY KEY, position INTEGER NOT NULL)

Tables for each classes.

Page 62: PofEAA and SQLAlchemy

class Player(Base):

__tablename__ = 'player'

id = Column(INTEGER, primary_key=True)

type = Column(INTEGER, nullable=False)

SOCCER_PLAYER = 1

BASEBALL_PLAYER = 2

__mapper_args__ = {'polymorphic_on': type}

class SoccerPlayer(Player):

__tablename__ = 'soccer_player'

id = Column(ForeignKey('player.id'), primary_key=True)

position = Column(INTEGER, nullable=False)

__mapper_args__ = {

'polymorhic_identity': Player.SOCCER_PLAYER}

Page 63: PofEAA and SQLAlchemy

Concrete Table Inheritance

Player

SoccerPlayer BaseballPlayer

CREATE TABLE soccer_player( id INTEGER PRIMARY KEY, name VARCHAR(32) NOT NULL, position INTEGER NOT NULL)

CREATE TABLE baseball_player( id INTEGER PRIMARY KEY, name VARCHAR(32) NOT NULL, position INTEGER NOT NULL)

Tables for each concrete classes

Page 64: PofEAA and SQLAlchemy

Mix-inclass BasePlayer(object):

id = Column(INTEGER, primary_key=True)

name = Column(VARCHAR(32), nullable=False)

class SoccerPlayer(BasePlayer, Base):

__tablename__ = ‘soccer_player’

position = Column(INTEGER, nullable=False)

Pros) Simple.Cons) You can’t get support from SQLAlchemy

Page 66: PofEAA and SQLAlchemy

10. Data Source Architectural Patterns

● Table Data Gateway

● Row Data Gateway

● Active Record

● Data Mapper

● Architectural Pattern and SQLAlchemy

Page 67: PofEAA and SQLAlchemy

Table Data Gateway

Target: Split querying from your domain logicHow: Create gateway for each table.

class PlayerGateway:

def find_by_id(self, id)

def find_by_name(self, name)

def update(self, id, name=None, age=None)

def insert(self, id, name, age)

Page 68: PofEAA and SQLAlchemy

Row Data Gateway

Target: Split querying from your domain logicHow: Create gateway for each record.

class PlayerGateway:

@classmethod

def find_by_id(cls, id):

… return cls(id, name, age)

… def insert(self)

def update(self)

Page 69: PofEAA and SQLAlchemy

Active Record

Row Data Gateway with Domain Logic

class Player:

@classmethod

def find_by_id(cls, id)

...

def birthday(self):

self.age += 1

Page 70: PofEAA and SQLAlchemy

Data Mapper

Target: Split out column/attribute mapping code from your domain logic.

class PersonMapper(BaseMapper):

def map(self, row):

return Person(id=row.id,

name=row.name,

age=row.age)

Page 71: PofEAA and SQLAlchemy

Architectural Patternsand SQLAlchemy

My personal consideration

Page 72: PofEAA and SQLAlchemy

Querying in SQLAlchemy

Unit of Work handles most of update and insert queries.

Easy query can be written very easy.(Building complex query is hard)

Page 73: PofEAA and SQLAlchemy

Table Gateway?

Most tables doesn’t require Table Gateway.

When there are some complex queries,Table Gateway is good to have.

Page 74: PofEAA and SQLAlchemy

Row Data Gateway?Active Record?

Separating model object and row data gateway cause additional complexity.For example, does your model need identity map?

Person

Person

PersonRow

Page 75: PofEAA and SQLAlchemy

Active Record drawbacks

Problem: Good table design is not good class design always.My answer: Use structural patterns like Embedded Value.

P: Mixing database access and domain logic is bad idea.A: Use Table Gateway to separate them.

Page 76: PofEAA and SQLAlchemy

Fat Model (™)

Person.register(...) # class methodperson.unregister()person.request_friend(other_person_id)...

Page 77: PofEAA and SQLAlchemy

Simple ActiveRecord & Serviceclass AccountService:

def register(self, name, email, age,...)

def unregister(self, person_id)

class FriendService:

def request(self,

from_person_id, to_person_id)

Page 78: PofEAA and SQLAlchemy

Finder in ServiceWhen you use service classes, you may be able to put finders there and don’t use Table Gateway.

class PersonService:

...

def find_by_name(self, name):

return Session.query(Person). \

filter_by(name=name).all()

Page 79: PofEAA and SQLAlchemy

Anemic Domain Model

http://www.martinfowler.com/bliki/AnemicDomainModel.html

P: Implementing all domain logic in service is not a OOP.

A: Simple Active Record + Medium sized classes + Service

Page 80: PofEAA and SQLAlchemy

When you design large application or framework,

PofEAA helps you lot.