[Catalyst] best practices for Catalyst development

Dominique Quatravaux dom at idealx.com
Fri Aug 12 14:13:10 CEST 2005


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

David Storrs wrote:

> Yes, it may. I'm quite prepared to believe it. I'm also prepared
> to believe that there really is a difference. Can we talk about
> it and come up with reasons for and against, or do we need to pick
> one view and take it as received wisdom?

In hindsight I understand my message could be understood as a cheap
shot, which was not intended to be - Please accept my apologies.

> Could you, or Perrin, point me at his arguments?

Well, see his own answer, AIUI he mostly has some diverging opinion
wrt. the M / C frontier. I concur to his very pragmatic point that not
allowing the business-logic to see the stash is key to its reuseability.

> I've seen a lot of assertions, but so far I don't believe anyone
> in the thread-- including me--has said "I do XYZ because of ABC.
> Here are the pros and cons that I evaluated to make this
> decision."
>
Actually I have some difficulty articulating the pros / cons of my
design choices (especially the cons :-) and Perrin does a much better
job of that (and while being orders of magnitude more helpful and polite).

Let me try nonetheless using a practical example: right now I'm doing
some kind of a permissions management GUI, which allows one to define
Groups, Users, Privileges, attach Privileges to Groups and Groups to
Users. Groups can also inherit their Privilege set from each other
(e.g. "tech director" inherits from "tech project leader" while adding
Privilege READ-WRITE to ALL_ACCOUNTS in the "project management app"
on top of that). This is my first Catalyst app.

I had to solve the following architectural problems (roughly by order
of appearance):

1) Persistence

    * what I did = I drafted a SQL schema and painted some Class::DBI
      over it (MODEL only).
    * Pro = it was quick for simple stuff.
    * Con 1 = it was not straightforward enough for me for has-many
      relationships (I ended up coding a pair of overengineered
      extension packages to Class::DBI, wasting a fair amount of time).
    * Con 2 = my code now smells SQL and I don't like that. I was
      expecting this difficulty (also known as the object-relational
      impedance mismatch). E.g. a Privilege "has a" Role because in
      the SQL schema the Privilege table has a "Role" column, even
      though it doesn't fit my abstraction (it's the other way around
      in my head, e.g. I want to do Privilege algebra calculations
      without taking Roles into account in any way). With yet more
      overengineering, I have a plan to get past this, though.
    * Conclusion = SQL bends my Model, I get over it.

2) Basic CRUD for the 3 main classes (Privileges, Groups, Users)

    * what I did = I pulled off some conventional Catalyst
      (CONTROLLER) and Template::Toolkit (VIEW). I plundered Maypole's
      template set.
    * Pro = it Just Works and it's a pleasure to code. The ->forward()
      primitive is groovy, it allows to do multi-page wizards in a
      straightforward way. The Maypole templates and CSS look great.
      The URL -> action mapping done as function attributes is
      terribly cute, Perlish, straight in the party line. I love it!
    * Con = Catalyst being a framework of multiple parts, ironing out
      some bugs required me to deep dive into the source code of a lot
      of CPAN modules, which was a very time consuming task. I chalk
      this up as an investment (this was also my first venture with
      Class::DBI). I don't have any issue with the resulting software
      architecture, however.
    * Conclusion = for *simple* Web apps, the Model is indeed so thin
      that you can see through it (no "business" in Model yet). But it
      won't last! (See below).

3) Constraint checking

    * Problem = we have to prevent loops of sub-Roles from appearing
      and the like.
    * What I did = I implemented that within the MODEL
      (MyApp::M::Privilege), using Class::DBI constraints.
    * Pro = I'm firmly convinced that the MODEL is where this stuff
      belongs. A cron job has no more right to create a loop than the
      GUI has, plus this is a whole-app coherence issue that SQL
      cannot (easily) capture.
    * Con = if for some reason another app (not in Perl) is later
      given direct read-write access to the SQL database, it will be
      able to mess up with the model constraints. Not that I want to
      do that in any case ! See below.
    * Why not in SQL = I hate expressing constraints in SQL (the
      standard thing for proponents of thin-model architectures). SQL
      constraints are cumbersome, non portable between DB's and quite
      limited in power, in a word they solve the wrong problem (making
      the SQL-level access an API graft point, a big no-no if you want
      any kind of upgradeability in your app !) in the wrong way (by
      designing yet another programming language - I have Perl, thank
      you very much).
    * Conclusion = now we are beginning to see the autonomous
      usefulness of the Model, because our app has evolved beyond a
      simple SQL table editor.

4) GraphViz

    * Problem = I want to display the graph of Roles that inherit from
      each other.
    * What I did = I coded MyApp::V::Role (VIEW, and not
      Template::Toolkit) and I called that class from
      MyApp::C::Role::graph : Path("graph.png")
    * Pro = I can draw graphs from the command line, using the same
      Model and View.
    * Con = N/A
    * Conclusion =My code separation is the right thing, it just
      showed reuseability.

5) Leveling the problem space : there is a world beyond CRUD

    * Problem = I want the end user to be able to set Roles *and*
      Privileges on the same screen (pointy-haired boss wants to
      *remove* Privileges even though his victim theoretically belongs
      to the adequate Role, grin)
    * What I did = I implemented a slew of dedicated methods in
      MyApp::M::User (MODEL) that deal with Privileges and Roles
      (->has_privilege(), ->add_privilege(), ->subtract_privilege()
      and so on) in terms of the already existing persistent
      accessors. I also implemented Privilege algebra operations in
      MyApp::M::Privilege (->compare(), ->simplify_list()). SQL bent
      my Model some more. I then implemented non-standard CONTROLLER
      and VIEW screens for His Pointy-Hairedness, using these as
      primitives.
    * Pro = my model classes are now meaty, ready for unforeseen
      future feature requests, still their exported interface really
      captures useful stuff that I want to do with them (I know,
      because my Controller calls them).
    * Con = I have to think and code a lot, and in steps (first design
      Model, then test it, then code it, then code Controllers, then
      test them with bogus debugging Views, then code the real Views
      and finally do acceptance testing). Catalyst is no longer
      shouldering the CRUD for me.
    * Conclusion = The gap between problem space and solution space
      becomes thicker, and my workload goes up. Nothing unexpected.

6) Looking at it all in hindsight

    * What I have = a Model that mixes persistence and business logic,
      and blends them together without separation of sub-concerns (the
      code that solves the "has a Role" problem lives in the same
      package, albeit under a different POD section, that the one that
      does Privilege algebra).
    * Pro = this is very easy to unit-test
    * Con = I fear that this lack of separation between concerns in
      Model will come and haunt me later
    * Conclusion = I'm not sure.

Global conclusion

    * I'm positive I have got the correct boundaries set between M, V,
      and C because I'm already reaping the benefits (unit tests,
      reuseability). Perhaps M could use more internal refactoring,
      I'm not sure yet.
    * the typical web app is not an adequate showcase for MVC: CRUD is
      just too simple. Only when your app has grown in complexity, can
      you be sure that your design is good. You could get away with
      inappropriate MVC if doing shopping carts (no offence intended
      to anybody - the challenging stuff is elsewhere, eg. scalability).
    * It seems that each time I upgrade my app, SQL gets in the way. I
      know it, I fear it, but there is little I can do about it. I
      could bend the SQL to fit my Perl instead of the other way round
      (Tangram) but then my database schema would lose legibility and
      my boss would not like it. (Open-source adequately-hyped
      SQL-less OODBMS for Perl anyone?)

> Also, this discussion is aimed at **best practices**. We all know
> that in the real world of short deadlines, we take shortcuts. This
> is about how you would do it if you were doing it The Right Way(tm)
>
Actually I have some R&D time alotted for this so I'm doing the best I
can for quality. I'm trying to acquire adequate tools and reflexes for
the time I'll be using Catalyst in crash-landing projects.

- --
Dominique QUATRAVAUX                           Ingénieur senior
01 44 42 00 08                                 IDEALX

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.5 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFC/JJWMJAKAU3mjcsRAkrEAJ94kKSfho/TuukGVCATuHUggCZ/OgCbBhEB
7HWAiuGaimSDgSiKL2ZqULQ=
=9wiK
-----END PGP SIGNATURE-----





More information about the Catalyst mailing list