Handel Framework
A simple commerce framework with AxKit/TT/Catalyst support.
Christopher H. Laco
Author. Hacker. Eternal Perl Newbie.
What It Is
- German for commerce
- Framework/reusable modules
- Cart manager:
- Add, edit, delete, search, save, restore
- Order manager:
- Add, edit, delete, search, save
- Checkout pipeline:
- Plugin based order manipulator
- Built in layers:
- Schema + Storage + Public Interface.
[any material that should appear in print but not on the slide]
What It Isn't
- Simple :-)
- Content Management System
- Cart32
- Intershop
- OpenInteract
- WebGUI
- "Out of the Box" solution
[any material that should appear in print but not on the slide]
Goals
- Database Agnostic:
- Any database
DBIx::Class supports
- Schema Agnostic:
- Use
Handel::Schema or existing schema
- Storage Agnostic:
- DBIC, XML, SOAP, Write Your Own!
- Checkout Agnostic:
- No two checkout processes are alike
- Implementation Agnostic:
- Not tied to the web. Just data access.
- Command line, Tk/GUI, SOAP/XMLRPC
[any material that should appear in print but not on the slide]
Features / Buzzwords
- Easy Integration
- Interchangable Schemas
- Swappable Storage Classes
- Column Constraints
- Column Validation
- Column Default Values
- Currency Formatting/Conversion
- Localization
[any material that should appear in print but not on the slide]
Easy Integration
- Catalyst Helpers
$ myapp_create.pl Handel::Scaffold
$ script/myapp_server.pl
http://localhost:3000/cart/
- AxKit Taglibs
xmlns:cart="...Handel/Cart" / <cart:add>
- Template Toolkit plugins
- [Your Framework Here]
- Jifty?
- Gantry?
- *cough*Maypole*cough*
[any material that should appear in print but not on the slide]
Interchangable Schemas
- Use existing
DBIx::Class schema
- Different field names work
- Map columns using
accessor
- No direct column access
- Some exceptions: total = price * quantity
- Subclasses fill the gaps
[any material that should appear in print but not on the slide]
Interchangable Schemas: Accessor
package My::Schema::Cart;
use base qw/Handel::Schema::Cart/;
__PACKAGE__->remove_columns('name');
__PACKAGE__->add_columns(my_name => {accessor=>'name'};
1;
package My::Schema::Cart;
use base qw/DBIx::Class/;
__PACKAGE__->table('my_carts');
__PACKAGE__->add_columns(qw/id shopper type description/);
__PACKAGE__->add_columns(my_name => {accessor=>'name'};
__PACKAGE__->has_many(
items => 'Handel::Schema::Cart::Item',
{'foreign.cart' => 'self.id'}
);
1;
Handel::Cart->storage->schema_class('My::Schema::Cart');
[any material that should appear in print but not on the slide]
Interchangable Schemas: Subclass
package MyCart::Item;
use base qw/Handel::Cart::Item/;
# schema class has qty instead of quantity
__PACKAGE__->storage->schema_class('My::Schema::Cart::Item');
# make quantity to keep total (quantity * price) / restore happy
sub quantity {
return shift->qty(@_);
};
# or, tweak total and restore
sub total {
my $self = shift;
return $self->qty * $self->price;
};
1;
[any material that should appear in print but not on the slide]
Interchangable Schemas: The Works
- Setting storage options in cart is easy
- Setting storage options in cart is dangerous
- Use different storage? Must change cart class.
- Create storage subclasses and set options there
- Create cart/order subclasses
- Custom cart uses custom storage
- Storage subclass changes. Cart subclass doesn't.
- Don't pollute all instances of Handel
[any material that should appear in print but not on the slide]
Swappable Storage Classes
- Layer between interface and raw storage
- Abstracts common actions
- Create, Search, Delete Carts/Orders
- Add, Search, Delete Items
- Returns generic result objects
- Cart/Order acts on generic result objects
- Lazy or Automatic updates
- Store carts/orders anywhere
- DBIx::Class / Class::DBI / DBI / XML
- SOAP / XMLRPC
- Google Checkout API
[any material that should appear in print but not on the slide]
Swappable Storage Classes: Generic Storage
package MyFileStorage;
use base qw/Handel::Storage Class::Data::Accessor/;
__PACKAGE__->mk_classaccessor('file_name');
use MyFileMagic;
sub create {
my ($self, $data) = @_;
my $file = MyFileMagic->new($self->file_name, $data);
return $self->result_class->create_instance($file, $self);
};
sub add_item {
my ($self, $result, $data) = @_;
my $file = $result->result_storage;
my $item_file = MyFileMagic->new(
$self->item_storage->file_name, $data);
return $self->result_class->create_instance($item_file, $self);
};
[any material that should appear in print but not on the slide]
Swappable Storage Classes: Subclasses
package MyCartStorage;
use base qw/MyFileStorage/;
__PACKAGE__->file_name('carts.dat');
__PACKAGE__->item_storage_class('MyCartItemStorage');
1;
package MyCartItem::Storage;
use base qw/MyFileStorage/;
__PACKAGE__->file_name('cart_items.dat');
1;
Handel::Cart->storage_class('MyCartStorage');
Handel::Cart::Item->storage_class('MyCartItemStorage');
# writes cart to carts.dat
my $cart = Handel::Cart->create({...});
[any material that should appear in print but not on the slide]
Swappable Storage Classes: Again...
- Create generic storage classes
- Subclass generic classes for Cart / Order / Items
- Set storage options in subclasses only
- Don't pollute all instances of Handel
- That's a lot of subclassing...so...
[any material that should appear in print but not on the slide]
Subclassing Made Easy: Module::Starter
$ ./module-starter --class=Module::Starter::Handel
Created MyProject
Created MyProject\lib\MyProject
Created MyProject\lib\MyProject\Cart.pm
Created MyProject\lib\MyProject\Cart
Created MyProject\lib\MyProject\Cart\Item.pm
Created MyProject\lib\MyProject\Storage
Created MyProject\lib\MyProject\Storage\Cart.pm
Created MyProject\lib\MyProject\Storage\Cart
Created MyProject\lib\MyProject\Storage\Cart\Item.pm
Created MyProject\lib\MyProject\Order.pm
Created MyProject\lib\MyProject\Order
Created MyProject\lib\MyProject\Order\Item.pm
Created MyProject\lib\MyProject\Storage\Order.pm
Created MyProject\lib\MyProject\Storage\Order
Created MyProject\lib\MyProject\Storage\Order\Item.pm
Created MyProject\lib\MyProject\Checkout.pm
...
[any material that should appear in print but not on the slide]
Column Constraints
- Define Constraints
- Failures Go Boom
[any material that should appear in print but not on the slide]
Column Validation
- Define Validation Profile
-
# FormValidator::Simple style profile
Handel::Cart->storage->validation_module('FormValidator::Simple');
Handel::Cart->storage->validation_profile([
name => [ ['NOT_BLANK'], ['LENGTH', 3, 10] ]
]);
-
# Data::FormValidator style profile
Handel::Cart->storage->validation_module('Data::FormValidator');
Handel::Cart->storage->validation_profile({
required => ['name']
});
- Failures Go Boom
[any material that should appear in print but not on the slide]
Column Default Values
- Define Default Values
- Values set during creation
[any material that should appear in print but not on the slide]
Currency Formatting and Conversion
- Currency columns are objects
- Chain calls to convert
- Uses
Locale::Currency::Format
- FMT_STANDARD = 1,000.00 USD
- FMT_SYMBOL = $1,000.00
- FMT_COMMON = 1.000 Dong (Vietnam)
- FMT_HTML = £1,000.00
- FMT_NAME = 1,000.00 US Dollar
- Set
conversion_class to use another conversion class
[any material that should appear in print but not on the slide]
Localizaton (L10N)
- No real user interface bits
- Localize exception messages
- Exceptions fully buzzword compliant!
[any material that should appear in print but not on the slide]
More Information
[any material that should appear in print but not on the slide]