http://qs1969.pair.com?node_id=262192


in reply to CPAN Module Proposal: Business::Ship

* Namespace: is Business::Ship a good spot for this? Alternatives?

I guess it depends somewhat on your long term plans for this. If it were just this, I'd be inclined to use something a bit more descriptive. Like, Business::Shipping::Calculator or Business::Shipping::Cost or something.

* Technology/other: How is the design? Good, bad, ugly? (Hubris question)

Well, honestly I didn't get far past the fact that even the POD didn't display cleanly in an 80 char wide terminal... In fact, the POD seems to be pretty well broken, at least for the Ship.pm file. So, I took a look, but not a very deep one.

I noticed there was sometimes a mix of data of code... your %country_translator hash buried in a subroutine in Business::Ship::Package, for instance.

I see you are trying to abstract out common things about packages in Business::Ship::Package and trying to include information about packages required by a specific shipper Business::Ship::<carrier>::Package. I wonder if it might not make more sense to just have a single package class which can hold all the information that might be required by a specific carrier. I certainly don't have a good overall picture of your design yet, but separating that stuff seems to be more complicated than necessary. Afterall, a package is a package is a package no matter who ships it.

Ideally, this would be written so that if you wanted to add Airborne Express next week, you could just write a Business::Ship::Airborne::Rates module and drop it in. I'm not familiar enough with the problem space (it's probably pretty messy) to decide how easy or hard that would be. Just the same, that's what I'd strive for.

Some thoughts on the overall design: I'm picturing a package class and various shipper classes. The Shipping module itself should be able to be queried for what shippers it handles. Each shipper should be able to be queried for what services it provides... maybe code would look like this:

use Business::Shipping; # Query the module for installed shipper specific modules. my @shippers = Business::Shipping::available_shippers(); my %shipping_option; for my $shipper (@shippers) { # Services would be Ground, 2nd day air, things like that. my @services = $shipper->services(); for my $service (@services) { # The $package would have been previously generated. $shipping_option{$shipper->name}->{$service} = $shipper->rate($service, $package); } }
Of course, that's just an illustration. It might be better to compress the rate sub to return a service/rate matrix for the package directly rather than having to build one. Maybe it would work better to return a base cost and modifiers for some service specific options (e.g. insurance.) After the package is actually scheduled for shipping, you can attach shipper specific information to the package itself, so it can be tracked, for instance. Maybe the available_shipper() sub could take an argument like "Saturday_delivery" and, in that case, would only return shippers that delivered on Saturday...

If you want to make it a good CPAN module, consider how people will want to use it. It's relatively easy to consider only how you want to use it but other people will likely want to use it differently.

Good luck with it.

-sauoq
"My two cents aren't worth a dime.";

Replies are listed 'Best First'.
Re: Re: CPAN Module Proposal: Business::Ship
by danb (Friar) on Jun 01, 2003 at 16:48 UTC
    Thanks a lot for the input, sauoq.
    I guess it depends somewhat on your long term plans for this. If it were just this, I'd be inclined to use something a bit more descriptive. Like, Business::Shipping::Calculator or Business::Shipping::Cost or something.

    What if the same module were going to be doing a lot of other features as well? (Like the ones I mentioned here).

    The various features don't seem to lend themselves to a common designation, so even the vendors come up with vauge references to them: "UPS Online Web Tools" ("Online Web Tools!?", now *that* is vague). The problem is that they *do* easily lend themselves to common technical details (they all generate similar XML, etc.).
    Well, honestly I didn't get far past the fact that even the POD didn't display cleanly in an 80 char wide terminal... In fact, the POD seems to be pretty well broken, at least for the Ship.pm file. So, I took a look, but not a very deep one.
    Heh heh heh... I went to all the trouble to write the POD, then I forgot to even test it. :-)
    I see you are trying to abstract out common things about packages in Business::Ship::Package and trying to include information about packages required by a specific shipper Business::Ship::<carrier>::Package. I wonder if it might not make more sense to just have a single package class which can hold all the information that might be required by a specific carrier. I certainly don't have a good overall picture of your design yet, but separating that stuff seems to be more complicated than necessary. Afterall, a package is a package is a package no matter who ships it.
    Perhaps, I'll have mull over that for a little while. "Package" doesn't represent a physical package, really, but more of a "what does the carrier think of a package as?"

    Some carriers think that each package in a "shipment" should know the destination, origination, etc. (i.e. Packages are all treated as individual shipments). Other vendors think of a package as only one part of a larger shipment.
    Ideally, this would be written so that if you wanted to add Airborne Express next week, you could just write a Business::Ship::Airborne::Rates module and drop it in. I'm not familiar enough with the problem space (it's probably pretty messy) to decide how easy or hard that would be. Just the same, that's what I'd strive for.
    Yes, that's exactly what I'm striving for -- so I really appreciate your advice :-). I guess having the Package object is an additional piece of complexity.

    However, I think it's likely that each carrier will have a different expectation for "package" anyway (that has been my experience so far), so to add a new module, one would have to modify the Package handling anyway, and I think having their own derivitive of Business::Ship::Package would be better than having the same code be everything to everyone.
    use Business::Shipping; # Query the module for installed shipper specific modules. my @shippers = Business::Shipping::available_shippers(); my %shipping_option; for my $shipper (@shippers) { # Services would be Ground, 2nd day air, things like that. my @services = $shipper->services(); for my $service (@services) { # The $package would have been previously generated. $shipping_option{$shipper->name}->{$service} = $shipper->rate($service, $package); } }
    Great idea! Part of that feature (like "what services are available for xyz vendor?") are specifically supported by some vendors' XML API.
    Again, I really appreciate the feedback.

    -Dan
      What if the same module were going to be doing a lot of other features as well?

      I agree with Aristotle on this. Something like Business::Shipping is more obvious than Business::Ship is. I too had a moment of uncertainty when I first saw the node title. Your intent is to implement a generic "shipping" API right? :-)

      "Package" doesn't represent a physical package, really, but more of a "what does the carrier think of a package as?"

      I guess I'm suggesting that maybe the package really should represent the physical package. All packages have things that, at least from your perspective (or that of any client using your module), are the same no matter what shipper is chosen. Packages have an origin, a destination, a weight, dimensions, contents, a value, etc. None of those is determined by the shipper. Collectively, these variables represent everything you know about the package; if the shipper were to ask you questions about the package, this is the information you'd generate your answers from.

      Other vendors think of a package as only one part of a larger shipment.

      So, it sounds like a "shipment" is another useful abstraction. It would be shipper-specific and so you probably would have a Business::Shipping::<carrier>::Shipment package defined for each carrier. A shipment would contain one or more packages. It would have an origin or destination just as the packages it contains. In fact, you'd probably want to assert that the packages in a shipment all had the same origins and destinations. The shipment would have a schedule attached to it. You'd likely track the shipment (rather than tracking a package.) I suspect you would want to avoid instantiating a Shipment until after one is actually scheduled; you shouldn't need these classes to determine the shipping rates.

      One of the keys to building a successful API is to base it around the perspective of the API's consumers. FedEx isn't going to be using your API, www.yetanotherlittlewebstore.com is... and they don't care how the individual shippers think of packages (or shipments.) They just want to be able to write code that naturally follows from the way they handle packages. Your API should hide the differences between shippers.

      When you walk into the Post Office or a FedEx office, you say "here's my package and this is where I want it to go; how much will that cost?" Your API should be true to that. If a shipper requires more specific information, your clients (those using your module) should be able to query the shipper to determine what information is needed. That way YALWS.com can ask those questions directly of their customers via some inputs on a form.

      I hope that better explains why I'm wary of your current <carrier>::Package" objects.

      -sauoq
      "My two cents aren't worth a dime.";
      

        I agree with Aristotle on this. Something like Business::Shipping is more obvious than Business::Ship is. I too had a moment of uncertainty when I first saw the node title. Your intent is to implement a generic "shipping" API right? :-)

        Well, it's decided then. Business::Shipping it is.

        "Package" doesn't represent a physical package, really, but more of a "what does the carrier think of a package as?"

        I guess I'm suggesting that maybe the package really should represent the physical package. All packages have things that, at least from your perspective (or that of any client using your module), are the same no matter what shipper is chosen. Packages have an origin, a destination, a weight, dimensions, contents, a value, etc. None of those is determined by the shipper. Collectively, these variables represent everything you know about the package; if the shipper were to ask you questions about the package, this is the information you'd generate your answers from.

        Very good ideas... thanks. I might try to go with a simpler 'Package' idea like you are suggesting. Unfortunately, the vendors really do have different ideas of what you need to know about a package. I mean, we could have one "perfect" package that can be everything to everyone, but UPS doesn't need:

        * Is it machineable?

        * Container? (not to be confused with "Package type" )

        * Breadth?

        Whereas USPS does. Then, probably, FedEx has some additional questions about packages that the other two don't.

        But anyway, I was hoping to design it so that the user never had to instantiate a ::Package object anyway -- they are just created behind the scenes.

        Other vendors think of a package as only one part of a larger shipment.

        So, it sounds like a "shipment" is another useful abstraction. It would be shipper-specific and so you probably would have a Business::Shipping::<carrier>::Shipment package defined for each carrier. A shipment would contain one or more packages. It would have an origin or destination just as the packages it contains. In fact, you'd probably want to assert that the packages in a shipment all had the same origins and destinations. The shipment would have a schedule attached to it. You'd likely track the shipment (rather than tracking a package.) I suspect you would want to avoid instantiating a Shipment until after one is actually scheduled; you shouldn't need these classes to determine the shipping rates.

        That is a *great* idea! I sure appreciate the feedback. This one is going on the todo list.

        One of the keys to building a successful API is to base it around the perspective of the API's consumers. FedEx isn't going to be using your API, www.yetanotherlittlewebstore.com is... and they don't care how the individual shippers think of packages (or shipments.) They just want to be able to write code that naturally follows from the way they handle packages. Your API should hide the differences between shippers.

        When you walk into the Post Office or a FedEx office, you say "here's my package and this is where I want it to go; how much will that cost?" Your API should be true to that.

        I guess I could have a "use_defaults=1" option that will just guess various things, like "is it residential", or "is it machineable", etc., that the API user might not care about (and doesn't have much influence on the final price). My first instict was to require every detail that the carrier requires, though.
        If a shipper requires more specific information, your clients (those using your module) should be able to query the shipper to determine what information is needed. That way YALWS.com can ask those questions directly of their customers via some inputs on a form.
        That's a nice idea, but I don't think it would work out for everything, in practice. The consumer would likely put in whatever values that caused it to come out cheaper; in which case the vendor would end up manually correcting it anyway (or eating the cost).

        Now that I think about it, you were probably thinking of things like "which service do you want" and "do you want insurance?", etc. Yes... that makes a lot of sense.

        I hope that better explains why I'm wary of your current <carrier>::Package" objects.

        Roger that. :-) -Dan