Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl modules hierarchy and composition

Reading more and more about Perl, I'm having doubts about how I organized my modules on my current project.

I have a main namespace - let's call it "MyProject".

In this project, the base data are graphs in which each object has a class. objects are linked with relations. Both objects and relations can have attributes.

There is a part of the project where I use a model to validate these graphs. So I have a Model class that is composed of objects of class Class, Relation and Attribute.

Classes Class, Relation and Attribute are used by class Model only, so it made sense to me to organize the modules as follows:

  • MyProject::Model
  • MyProject::Model::Class
  • MyProject::Model::Relation
  • MyProject::Model::Attribute

But I'm starting to think that it will make sense to me only if I ever dare to relase parts of my project in CPAN. I think people will believe that Class, Relation and Attribute are inheriting Model and not composing it.

So: shall I reorganize my modules this way:

  • MyProject::Model
  • MyProject::Class
  • MyProject::Relation
  • MyProject::Attribute

Or maybe indicate that Class, Relation and Attribute are parts of Model by appending their names?

  • MyProject::Model
  • MyProject::ModelClass
  • MyProject::ModelRelation
  • MyProject::ModelAttribute

My question: What is the currently considered a best practices for module organization and naming when it comes to composition?

Cross-posted at Perlmonks

like image 813
David Verdin Avatar asked Jan 04 '23 19:01

David Verdin


1 Answers

Your concern is correct. Typically, packages are named by either inclusion or inheritance. I'm going to make up a more realistic example. Let's say we are building an online shop.

Inclusion

You first pick a namespace for your project. In the business world, you often see namespaces with the company name first, to distinguish proprietary modules from CPAN ones. So the project's namespace could be:

OurCompany::Shop

Then probably the main class or module for the application is called the same, so we have a

OurCompany/Shop.pm

We will have a bunch of things that we need to make an online shop. If our project is MCV, there are controllers and models and stuff like that. So we might have these things:

OurCompany::Shop::Controller::ProductSearch
OurCompany::Shop::Controller::Cart
OurCompany::Shop::Controller::Checkout
OurCompany::Shop::Model::Database

All of those map to modules direcly.

OurCompany/Shop/Controller/ProductSearch.pm
OurCompany/Shop/Controller/Cart.pm
OurCompany/Shop/Controller/Checkout.pm
OurCompany/Shop/Model/Database.pm

But there is no OurCompany::Controller as a base class. That name is just a namespace to sort things into.

Then there are some things that are just there, and get used by OurCompany::Shop, like the Session engine.

OurCompany::Shop::Session

Those go on the first level after the project, unless they are very specific.

Now of course there is some kind of engine behind the session system. Let's say we are fancy and use Redis for our sessions. If we implement the communication ourselves (which we wouldn't because CPAN has done that already), we would stick that implementation into

OurCompany::Shop::Session::Engine::Redis

The only thing that uses this module is OurCompany::Shop::Session under the hood. The main application doesn't even know what engine is used. Maybe you don't have Redis on your development machine, so you are using plain files.

OurCompany::Shop::Session::Engine::File

Both of them are there, they belong to ::Session, but they don't get used by any other part of the system, so we file them away where they belong to.

Inheritance

We will also have Products1. The base product class could be this.

OurCompany::Shop::Product

And there is a file for it.

OurCompany/Shop/Product.pm

Just that this base product never gets used directly by the shop, other than checking that certain things have to have ::Product in their inheritance tree (that's an isa check). So this is different from ::Session, which gets used directly.

But of course we sell different things, and they have different properties. All of them have prices, but shoes have sizes and hard drives have a capacity. So we create subclasses.

OurCompany::Shop::Product::Shoe
OurCompany::Shop::Product::HardDrive

And those have their own files.

OurCompany/Shop/Product/Shoe.pm
OurCompany/Shop/Product/HardDrive.pm

We might also distinguish between a mechanical HardDrive and an SSD, so we make an ::SSD subclass.

OurCompany::Shop::Product::HardDrive::SSD

OurCompany/Shop/Product/HardDrive/SSD.pm

So basically things are put in the same namespace if they belong to each other. Here's a tree view of our lib.

.
└── OurCompany
    ├── Shop
    │   ├── Controller
    │   │   ├── Cart.pm
    │   │   ├── Checkout.pm
    │   │   └── ProductSearch.pm
    │   ├── Model
    │   │   └── Database.pm
    │   ├── Product
    │   │   ├── HardDrive
    │   │   │   └── SSD.pm
    │   │   ├── HardDrive.pm
    │   │   └── Shoe.pm
    |   ├── Product.pm
    │   └── Session.pm
    │   │   └── Engine.pm
    │   │       ├── File.pm
    │   │       └── Redis.pm
    └── Shop.pm

To sum up:

  • For your main module, the file name is the last part of the project's package name. Everything else goes under there.
  • Stuff that belongs together gets grouped under one namespace.
  • Stuff that inherits from something goes under that something's namespace.
  • Stuff that only gets used by one specific thing goes under that thing.

Never append things together like MyProject::AB to indicate that it belongs to MyProject::A. Always use namespace separators to organize your namespaces. Consider this, which looks just plain wrong:

OurCompany::ShopProductShoe

1) It might be that we also have a backoffice application that also uses the same product classes. In that case, we might have them as OurCompany::Product, and they get used by two different projects, OurCompany::Shop and OurCompany::BackOffice.

like image 92
simbabque Avatar answered Jan 13 '23 23:01

simbabque