Join the Stack Overflow Community
Stack Overflow is a community of 6.6 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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

share|improve this question
    
Indeed. Let me edit my question with actual classes. – David Verdin Jan 6 at 11:13
    
By composed of you mean that it uses objects of these classes? – simbabque Jan 6 at 11:23
    
Sorry for giving a misguiding example. Basically, it contains hashes of objects from these classes. – David Verdin Jan 6 at 11:25
    
Are ::Class, ::Relation and ::Attribute used anywhere else? Is there another top level thing in MyProject:: next to ::Model? – simbabque Jan 6 at 11:28
    
Yes, there are other things. Model is used by other modules. But it provides top-level methods, so that other modules don't use Class, relation or Attribute. Only Model does. – David Verdin Jan 6 at 11:30
up vote 8 down vote accepted

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.

share|improve this answer
1  
That's crystal clear, thanks! A last concern, though: Suppose I have computers made of disks, processor and ram (that's a dumb example). It would make sense to have a Computer.pm class. But I would still want to group computer elements in a dedicated namespace. In that case, should I: 1- create another namespace to hold components: ComputerComponent::Disk, ComputerComponents::RAM, ComputerComponents::CPU 2- Use the Computer namespace, which would go against your recommendations? – David Verdin Jan 6 at 11:11
    
@David hmm. If your main thing is Computer, then Computer::CPU, Computer::Disk and Computer::RAM make sense. But if besides the physical components, you also have classes that do something else, then maybe Computer, Computer::Component::CPU, Computer::Component::Disk, Computer::Role::Portable. If the components are only for the Computer, split it like that. They don't need to all ship with the Computer distribution, so they could be in different code repos, but if you want to make a new modul Computer::Component::Disk::SSD and release it, it's root will contain a bunch of empty folders first – simbabque Jan 6 at 11:16
    
OK, so in a composition, I can simply add a sub-namespace that will be used to hold components. that makes sense, thanks! – David Verdin Jan 6 at 11:23
    
@David yes, exactly. I'll add another piece of example to the answer. – simbabque Jan 6 at 11:29

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.