SQLAlchemy models isn't abstract layer / data model

48 views
Skip to first unread message

Jan Koprowski

unread,
Jan 21, 2009, 2:00:40 AM1/21/09
to pylons-discuss
In MVC *M*odel should be abstract layer of data and hide data
representation and real method access giving: universal, good
described, readable and simple interface to manage this data: add,
remove, get all, get one etc... methods.

Commonly I use Pylons. In the last lesson of my University of
Technology our lecturer show us Django and IMHO Django Models is much
more coherent then SQLAlchemy based model. Why ? In Django i "see"
only class methods like save or get etc... where i don't know what is
happend under the class method. This could be database, object
database or simple text file or even binary file. All what I need is
use the save method and when I want to move my database from, for
example: mysql tables data to my own binary records data file, only
what i must to do is reimplement save method, move data to my binary
files and I finished (of course i should re implement other methods
too). My application even doesn't know about any changes. It use save
method and all is fine :)

What's the "problem" with SQLAlchemy ? My class (for example User)
mapped by SQLAlchemy mapper doesn't have any additional methods. My
tool is meta.Session.query and my code use this. So, when I move my
data to my own binary file (for example) i must re implement whole
controllers code to use something new because meta doesn't have any
sense.

In my Pylons applications i define save, update and many other methods
in User class, which give my abstract layer, hiding meta.Session
inside, in methods. But this is extra job for programmer - doesn't
sounds nice :|

What do You think about this ?

Greetings from Poland
--
Jan Koprowski

Paweł Stradomski

unread,
Jan 21, 2009, 4:20:14 AM1/21/09
to pylons-...@googlegroups.com
W liście Jan Koprowski z dnia środa 21 stycznia 2009:

> In MVC *M*odel should be abstract layer of data and hide data
> representation and real method access giving: universal, good
> described, readable and simple interface to manage this data: add,
> remove, get all, get one etc... methods.
>
One could say that Active Record (django style) is a more leaky abstraction,
as it ties persistence to the domain model. Read more on orm patterns (Active
Record, Table/Row Gateway and Data Mapper in Fowler books).

Model layer should not be (IMHO) monolithic - data persistence should not be
tightly coupled to your domain model, while Active Record does exactly that.

So your model should have a low-level layer that does the persistence, and
high-level domain logic layer that deals with your application logic.

Also, your controllers should probably not use session object at all - you
could wrap all the high-level processes (eg. user creation, posting an
article etc.) in functions (maybe classmethods) or classes in the model
layer.

Just out of curiosity... which University of Technology? The Kraków one (AGH)?

--
Paweł Stradomski

chris mollis

unread,
Jan 21, 2009, 8:20:42 AM1/21/09
to pylons-...@googlegroups.com
Agreed.. data persistence should never be tied to the domain model.  That's precisely the problem with ActiveRecord (sic Django/RoR)
style of db development.  It's treated as an afterthought when in fact, the database is the most common point of contention within any web application and usually requires the most optimization (from 'back to front'.. not 'front to back').

Moreover, deviating from this approach presents incredible problems with development, requiring forks from the prescribed pattern..  

MilesTogoe

unread,
Jan 21, 2009, 9:25:25 AM1/21/09
to pylons-...@googlegroups.com
chris mollis wrote:
> Agreed.. data persistence should never be tied to the domain model.
> That's precisely the problem with ActiveRecord (sic Django/RoR)
whoa, first Django and RoR do work for thousands of people / sites.
The question is can something else work even better? The goal is what
is productive for users and still produces a responsive data model.

As I mentioned, I actually prefer the DataMapper approach where your
data definition is in the file along with your ORM functions - the 2
have to be in synch and this is the clearest way to do that. I also
really like the way DataMapper automagically handles migrations - you
change the definition and simply run an update. That is really
productive in an agile way. What I don't like about Django ORM is that
it automagically changes table names (puts the project name on the
front) and create foreign key fields without any "_id" which is much
clearer in terms of what you have in your data. In other words a good
ORM should work with existing SQL data tables and leave the tables in a
clear way.

I guess there is still the discussion of what is better: a specific
mapping vs "has_many" - the "has_many" is easier, more productive, the
mapping is clearer, less magic. Maybe someone has a way to do both?

Mark Hildreth

unread,
Jan 21, 2009, 9:48:15 AM1/21/09
to pylons-...@googlegroups.com
On Wed, Jan 21, 2009 at 2:00 AM, Jan Koprowski <Jan.Ko...@gmail.com> wrote:
>
>
> What's the "problem" with SQLAlchemy ? My class (for example User)
> mapped by SQLAlchemy mapper doesn't have any additional methods. My
> tool is meta.Session.query and my code use this. So, when I move my
> data to my own binary file (for example) i must re implement whole
> controllers code to use something new because meta doesn't have any
> sense.
>

Why do you have to implement them into the User object? Why can't you
do this....

r = UserRepository()
u = User("Me")
r.add(u)

I just saved a user to a database, all of my domain code is in the
User class, all of my persistence code is in the UserRepository class,
which itself deals with meta.Session. If later I want to switch from
storing in a database to storing in a data file, I only need to make a
new class and have the controllers use this new one instead.

You can also have UserRepository inherit from a BaseRepository class
with the typical Add, Update, Delete, etc. methods so that you don't
have to rewrite them over and over again. More importantly, you can
have custom functions, like r.GetUsersWithExpiredPasswords(). Because
the SQLAlchemy query code is encapsulated into the repository's
GetUsersWithExpiredPasswords method, your controller still works fine
when you want to change to a datafile repository, so long as you have
reimplemented these custom queries using your new persistence
mechanism (which you would've done anyway, just in your User class).

Michael Bayer

unread,
Jan 21, 2009, 10:58:37 AM1/21/09
to pylons-discuss
On Jan 21, 2:00 am, Jan Koprowski <Jan.Koprow...@gmail.com> wrote:
> In MVC *M*odel should be abstract layer of data and hide data
> representation and real method access giving: universal, good
> described, readable and simple interface to manage this data: add,
> remove, get all, get one etc... methods.
>
> Commonly I use Pylons. In the last lesson of my University of
> Technology our lecturer show us Django and IMHO Django Models is much
> more coherent then SQLAlchemy based model. Why ? In Django i "see"
> only class methods like save or get etc... where i don't know what is
> happend under the class method. This could be database, object
> database or simple text file or even binary file. All what I need is
> use the save method and when I want to move my database from,

The difference between myobject.save() and somepersistencething.add
(object); somepersistencething.commit() is that the latter allows
abstraction of the concept that your objects are even "saved" at all -
they are not part of any particular hierarchy tied to their
persistence implementation. As a bonus, the latter API allows an
atomic demarcation of many persistence operations whereas the former
does not. Neither example indicates the slightest thing to do with
what "happened under the method" and I don't see what you're referring
to. So I say SQLAlchemy's model is "more coherent".

Jan Koprowski

unread,
Jan 21, 2009, 11:33:03 AM1/21/09
to pylons-discuss
@Paweł Stradomski
University of Technology in Gdańsk


Ok. I understood this. But - i must wrote this methods. Simple CRUD
isn't something bad. SQLAlchemy could support something like this:

class User(SQLAlchemy.CRUD):
pass


and give optionaly something like in django. This will be nice. Now i
do for example something like this.

def __new__(cls, *args, **kwargs):
if 'username' in kwargs.keys():
uid = getpwnam(kwargs.get('username')).pw_uid
if 'uid' in kwargs.keys():
uid = kwargs.get('uid')
# @todo - poprawic czytelnosc
if uid:
if meta.Session.query(Informations).get(uid) == None:
return object.__new__(cls, *args, **kwargs)
else:
return meta.Session.query(Informations).get(uid)
else:
return object.__new__(cls, *args, **kwargs)

IMHO this could be standard for all classes (why not). I understood
this SQLAlchemy forces you to write your own class methods wrap
meta.Session - but this isn't cool :P because i waste my time :P

Paweł Stradomski

unread,
Jan 21, 2009, 11:44:54 AM1/21/09
to pylons-...@googlegroups.com
W liście MilesTogoe z dnia środa 21 stycznia 2009:

> chris mollis wrote:
> > Agreed.. data persistence should never be tied to the domain model.
> > That's precisely the problem with ActiveRecord (sic Django/RoR)
>
> whoa, first Django and RoR do work for thousands of people / sites.
> The question is can something else work even better? The goal is what
> is productive for users and still produces a responsive data model.

Actually yes, there are things that can work better, eg. SQLAlchemy or
Hibernate. I don't know much about Django ORM, but pretty much about RoR
ActiveRecord.

Such a simple ORMs have many limitations, eg they assume one table - one
class. What about join-table inheritance? What about a db structure when one
object maps to three tables? Or one table to many objects (the last one is
supported in RoR actually). What about vertical and horizontal partitioning?

Similarily, they usually lack an identity map. When doing really complex
operations it is very probable you'll end with two object instances that
represent the same row - and it'll be a pain to solve.

Original poster mentioned situation, when he wants to switch from RDBMS to
another storage mechanism. Now, if that mechanism (eg files) requires some
objects to be kept together in one file... let's say a blog post and all the
comments in single file (stupid example, but will do for now). What about all
the calls to comment.save()? Should they just do post.save() or not? Will
that mean you save a single post many times in a single transaction?

Another thing, not strictly related to AR... code written with such a ORM
tends to put too much logic in persistence operation hooks, like
before/afterInserts, before/afterDelete etc. In the end, it's usually a bad
idea - those methods should just deal with persistence aspect, like data
denormalization - not even logging (I'm planning to write a post on that
later).

ActiveRecord works pretty well for simple schemas, but as the schema and
application logic grows, it becomes more and more difficult to use properly -
I mean applications where transactions span multiple classes, eg receiving CC
payment triggers checking customer account balance which fires multiple
no-longer-debtor hooks which re-activate customer's internet account which
allocates IP in the network which determines the pool is 90% full and sends
an e-mail to network operators etc... Fun starts at 100 tables in the system.

Cluttering domain logic code with persistence aspects is not nice.

--
Paweł Stradomski

Michael Bayer

unread,
Jan 21, 2009, 11:43:15 AM1/21/09
to pylons-discuss
there is absolutely no reason in the world you are "forced" to write
class methods, except for the fact that you want that particular
pattern. However, if you want that pattern, its utterly absurd to
believe you have to create those methods individually for every
class. They should be implemented on a base class of your choosing:

Session = scoped_session(sessionmaker())

class MyActiveRecordBase(object):
session = Session

def __new__(cls, *args, **kwargs):
if args and self.Session.query(cls).get(args) == None:
return object.__new__(cls, *args, **kwargs)
else:
return object.__new__(cls, *args, **kwargs)

def save(self):
Session.add(self)
Session.flush()

def delete(self):
Session.delete(self)
Session.flush()


also please be aware of the declarative extension at
http://www.sqlalchemy.org/docs/05/reference/ext/declarative.html in
case you find SQLA's Table construct similarly unappealing.

Paweł Stradomski

unread,
Jan 21, 2009, 11:48:24 AM1/21/09
to pylons-...@googlegroups.com
W liście Jan Koprowski z dnia środa 21 stycznia 2009:

> def __new__(cls, *args, **kwargs):
> if 'username' in kwargs.keys():
> uid = getpwnam(kwargs.get('username')).pw_uid
> if 'uid' in kwargs.keys():
> uid = kwargs.get('uid')
> # @todo - poprawic czytelnosc
> if uid:
> if meta.Session.query(Informations).get(uid) == None:
> return object.__new__(cls, *args, **kwargs)
> else:
> return meta.Session.query(Informations).get(uid)
> else:
> return object.__new__(cls, *args, **kwargs)
>

What exactly are you trying to achieve?

--
Paweł Stradomski

Michael Bayer

unread,
Jan 21, 2009, 12:04:16 PM1/21/09
to pylons-discuss


On Jan 21, 11:48 am, Paweł Stradomski <pstradom...@gmail.com> wrote:

> What exactly are you trying to achieve?

one of the tenets of activerecord is that constructing an object with
a primary key returns the existing object implicitly.

my example meant to read:

def __new__(cls, *args, **kwargs):
if args:
obj = self.Session.query(cls).get(args)
if obj:
return obj

return object.__new__(cls, *args, **kwargs)

Paweł Stradomski

unread,
Jan 21, 2009, 12:13:24 PM1/21/09
to pylons-...@googlegroups.com
W liście Michael Bayer z dnia środa 21 stycznia 2009:

> On Jan 21, 11:48 am, Paweł Stradomski <pstradom...@gmail.com> wrote:
> > What exactly are you trying to achieve?
>
> one of the tenets of activerecord is that constructing an object with
> a primary key returns the existing object implicitly.

Right, didn't think of it.

--
Paweł Stradomski

Mark Hildreth

unread,
Jan 21, 2009, 12:19:58 PM1/21/09
to pylons-...@googlegroups.com
2009/1/21 Jan Koprowski <Jan.Ko...@gmail.com>:

>
> Ok. I understood this. But - i must wrote this methods. Simple CRUD
> isn't something bad. SQLAlchemy could support something like this:
>
> class User(SQLAlchemy.CRUD):
> pass
>
>
> and give optionaly something like in django. This will be nice...
>
> [snip]

>
>
> IMHO this could be standard for all classes (why not). I understood
> this SQLAlchemy forces you to write your own class methods wrap
> meta.Session - but this isn't cool :P because i waste my time :P
>

I think others have pretty much drove the point home, but the reason
you think it's "more work" is because SA does X well, and you want it
to do Y. We can argue the point of X vs. Y, but ultimately it's your
decision. Perhaps you should check out elixir...

http://elixir.ematia.de/trac/wiki

Ross Vandegrift

unread,
Jan 21, 2009, 12:53:17 PM1/21/09
to pylons-...@googlegroups.com
On Wed, Jan 21, 2009 at 08:33:03AM -0800, Jan Koprowski wrote:
> IMHO this could be standard for all classes (why not). I understood
> this SQLAlchemy forces you to write your own class methods wrap
> meta.Session - but this isn't cool :P because i waste my time :P

Don't wrap meta.Session. That's a silly thing to do. The semantics
of the session and the sematics of your object are *completely*
seperate things. In SQLAlchemy, your model objects don't save
themselves - the session commits their data.

It seems like you're putting a square peg in a round hole.

--
Ross Vandegrift
ro...@kallisti.us

"If the fight gets hot, the songs get hotter. If the going gets tough,
the songs get tougher."
--Woody Guthrie

Jan Koprowski

unread,
Jan 21, 2009, 2:28:04 PM1/21/09
to pylons-discuss


On 21 Sty, 18:53, Ross Vandegrift <r...@kallisti.us> wrote:
> It seems like you're putting a square peg in a round hole.

I don't think so. My goal is just to made abstraction layer and this
forces me to wrap Session. Thats all. Methods line def add() wher i
use add and session method one by another doesn't give me transaction
goodnes. Mayby

with User() as user:
user.something = 'val'
user.year = 2009

will be good ?

Jan Koprowski

unread,
Jan 21, 2009, 2:34:09 PM1/21/09
to pylons-discuss


On 21 Sty, 20:28, Jan Koprowski <Jan.Koprow...@gmail.com> wrote:
> On 21 Sty, 18:53, Ross Vandegrift <r...@kallisti.us> wrote:
>
> > It seems like you're putting a square peg in a round hole.
>
> I don't think so. My goal is just to made abstraction layer and this
> forces me to wrap Session. Thats all. Methods line def add() wher i
> use add and session method one by another doesn't give me transaction
> goodnes. Mayby

I want to wrap ... session in User class not wrap session for session
it self :) of course

> with User() as user:
>   user.something = 'val'
>   user.year = 2009
>
> will be good ?

with User() as user:
user.username = 'johny'
user.add()

and meta.Session.commit() will be call at end of with statement :P
Reply all
Reply to author
Forward
0 new messages