Declarative, declarative 2 comments
เมื่อเช้านี้นั่งอ่าน Changelog ของ SQLAlchemy เลยได้เห็นถึงการมีอยู่ของ declarative plugins ที่ถ้าหากมองแล้วคงนึกถึง Elixir กันก่อน เพียงแค่มัน verbose กว่า Elixir มาก แต่หลังจากที่ดูความสามารถมันไปซักพักแล้วก็คิด “อื้ม! นี่แหละ ใช่เลย”
ปัญหาของผมที่มีกับ SQLAlchemy มานาน ก็คือเวลาที่จะสร้าง model ขึ้นมา จำเป็นที่จะต้องสร้าง table schema ขึ้นมา สร้าง Python class และสุดท้ายต้องไป map ทั้งสองอันนี้เข้าด้วยกันด้วย Mapper อีกที ถ้าลองเขียนดูก็ได้หน้าตาแบบนี้ออกมา
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | users_table = Table('users', metadata, Column('id', Integer, primary_key=True), Column('username', String), Column('password', String), Column('group', Integer, ForeignKey('groups.id'))) groups_table = Table('groups', metadata, Column('id', Integer, primary_key=True), Column('name', String)) class User(object): pass class Group(object): pass mapper(User, users_table, properties={'group': relation(Group)}) mapper(Group, groups_table, properties={'user': relation(User)}) |
ถ้าหากมีแค่ 5-6 ตาราง มันก็ยังดูแลได้ง่ายอยู่ แต่พอจำนวนตารางมากขึ้น จำนวนคอลัมน์ต่อตารางมากขึ้น การดูแลในลักษณะนี้ยิ่งทำให้อยาก rewrite ทั้งเว็บใน Rails อีกรอบมากขึ้นเท่านั้น — ในกรณีนั้นก็จะใช้ ActiveRecord และเขียนได้แบบนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class AddUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :username t.string :password t.integer :group end create_table :groups do |t| t.string :name end end def self.down drop_table :users drop_table :groups end end |
จริงๆ แล้ว migration ไม่ค่อยสำคัญสำหรับกรณีนี้เท่าไหร่ เพราะยังไง ActiveRecord ก็จะอ่าน schema เอาจากฐานข้อมูลอยู่แล้ว ดังนั้นสำหรับตัว model จริงๆ จึงหน้าตาสั้นๆ ง่ายๆ แค่นี้
1 2 3 4 5 6 | class User < ActiveRecord::Base belongs_to :groups end class Group < ActiveRecord::Base has_many :users end |
ไม่ว่าจะหลับตาข้างไหนดู ActiveRecord ก็ดูสวยงามกว่า SQLAlchemy หลายเท่า แต่แน่นอนว่า library ในลักษณะเดียวกันสำหรับ Python มันก็มี นั่นก็คือ Elixir ที่จะเป็น layer บน SQLAlchemy อีกที ถ้าหากเขียนในลักษณะเดียวกัน ก็จะได้แบบนี้
1 2 3 4 5 6 7 8 | class User(Entity): has_field('name', String) has_field('password', String) belongs_to('group', of_kind='Group') class Group(Entity): has_field('name', String) has_many('users', of_kind='User') |
ดูสะอาดกว่าวิธีปกติของ SQLAlchemy อยู่หลายเท่าตัว เพียงแต่ผมไม่อยากจะใช้ Elixir ด้วยเหตุผลว่ามันทำงานแทนให้เยอะเกินไปหน่อย ดังนั้นตัวเลือกสุดท้ายจึงตกมาที่ SQLAlchemy Declarative Plugin อธิบายง่ายๆ มันก็คือจุดกึ่งกลางระหว่างวิธีที่ Elixir ใช้ และวิธีปกติของ SQLAlchemy เอง
เวลาเขียนก็จะหน้าตาแบบนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class User(Base): __tablename__ = 'users' id = Column(Integer) name = Column(String) password = Column(String) group_id = Column(Integer, ForeignKey('groups.id')) group = relation("Group") class Group(Base): __tablename__ = 'groups' id = Column(Integer) name = Column(String) users = relation("User") |
พบกันครึ่งทาง ดูเป็นตัวเลือกที่เหมาะสมดีเพื่อการใช้ SQLAlchemy อย่างสบายใจ ข้อดีที่ได้เหนือ Elixir ขึ้นมาอีกหน่อยก็คือ ยังใช้งานพวก Column() หรือ relation() ได้ตามปกติเหมือนใน table schema และ Mapper แทบทุกประการ สำหรับ users_table หรือ groups_table เวลาเรียกใช้ในกรณีของการใช้งาน declarative plugin ก็จะกลายเป็น User.__table__ และ Group.__table__
เมื่ออ่าน document ไปอีกหน่อย ก็พบกับ
Relations to other classes are done in the usual way, with the added feature that the class specified to relation() may be a string name. The “class registry” associated with Base is used at mapper compilation time to resolve the name into the actual class object, which is expected to have been defined once the mapper configuration is used
เป็นความรำคาญของผมมาตั้งแต่ตอนเริ่มที่จะแยก model ออกเป็นส่วนๆ มีปัญหากับการ import เพื่อมาใช้ Mapper มาก ถ้าหากสามารถใช้ string ได้ ปัญหานี้ก็น่าจะหมดไปโดยสิ้นเชิง พอเห็นแบบนี้แล้วก็เปลี่ยนไปใช้แบบแทบไม่ต้องคิดทบทวนเป็นรอบที่สองเลย
2 Responses to 'Declarative, declarative'
Subscribe to comments with RSS or TrackBack to 'Declarative, declarative'.
-
อย่าเรียกว่าพี่เลยครับ ผมไม่ได้แก่ขนาดนั้น :)
เรื่อง source ของโปรเจคที่ทำตอนนี้ มีแผนจะเปิดซอร์สโค้ดอยู่แล้วในอนาคต แต่จำเป็นต้องปัดฝุ่นทำความสะอาด หลายๆ ส่วนก่อน จึงไม่สามารถเปิดได้ในเวลาอันใกล้นี้ครับ แต่ถ้าหากมีโปรเจคใหม่ที่พอจะเปิดได้ก็อยากจะเปิดเหมือนกัน
สวัสดีครับพี่
ผมกำลังหัดขียน Pylons อยู่ครับ โดยจะพยายามเอามันไปรันบน Google App Engine เจอ model mapping ของ SQLAlchemy ก็แทบแย่ บันทึกนี้ช่วยแนะแนวทางให้ผมได้ดีทีเดียว Pylons project ที่พี่ทำ แจก source ไหมครับ ผมจะได้ขอมาดูเป็นแนวทาง
ขอบคุณครับ