Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

CPAN Module Proposal: Business::Ship

by danb (Friar)
on Jun 01, 2003 at 08:12 UTC ( [id://262176]=perlmeditation: print w/replies, xml ) Need Help??

Business::Ship is a shipping API, currently it only implements shipping cost calculations for UPS and USPS. Before I upload it to PAUSE, I would like some feedback.

* Namespace: is Business::Ship a good spot for this? Alternatives?
- Related, but different modules are: Business::UPS, Business::FedEx, and Business::FedEx::DirectConnect.
* Technology/other: How is the design? Good, bad, ugly? (Hubris question)

The module in question can be found here:

http://www.kavod.com/Business-Ship
http://www.kavod.com/Business-Ship/Business-Ship-latest.tar.gz

Thanks,
--
Dan Browning, Kavod Technologies, <db@kavod.com> 360.843.4074x217
6700 NE 162nd Ave, Ste 611-210, Vancouver, WA. Random Fortune:
To be trusted is a greater compliment than to be loved.

Replies are listed 'Best First'.
Re: CPAN Module Proposal: Business::Ship
by sauoq (Abbot) on Jun 01, 2003 at 12:29 UTC
    * 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.";
    
      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.";
        
Re: CPAN Module Proposal: Business::Ship
by diotalevi (Canon) on Jun 01, 2003 at 12:09 UTC

    Your keywords are United States, Business, Shipping Costs, UPS and USPS. Business::Ship falls short of that. You could have an API that is implemented in Business::ShippingCost and have subclasses in Business::ShippingCosts::$CompanyName.

    I see that you've hardcoded various strings in your code like the following country name translator. You should either delegate this job to something else or move the data outside your program. I also notice you use the indirect object syntax - that's likely a mistake on your part and it should be rectified.

      Thanks for the input, diotalevi. The reason I picked Business::Ship was because calculating shipping cost isn't the only feature that I wanted to support. In the future, I plan to add:

      * Address Verification
      * Shipping status (tracking)
      * Electronic Merchandise Return
      * Shipping label generation
      * Customs forms
      * Shipping time (how long till it gets there)
      * Delivery/Signature confirmation

      I see that you've hardcoded various strings in your code like the following country name translator. You should either delegate this job to something else or move the data outside your program.

      Good advice, I'll do that.

      I also notice you use the indirect object syntax - that's likely a mistake on your part and it should be rectified.

      I like the object factory design a lot, so I probably used it even though it wasn't necessary. But the idea was that it would make it easier on the API user to use UPS or USPS (eventually others) without changing much code. Namely, they can get the sub-object without doing an eval. For an example, see doc/xps-query.tag.
      -Dan

        diotalevi said:

        also notice you use the indirect object syntax - that's likely a mistake on your part and it should be rectified.

        Your response seemed to indicate that you didn't really understand what he meant. "Indirect object syntax" refers to calling methods like so:

        my $obj = new Object(); # Indirect Object notation (class method) my $ret = some_method $obj; # Indirect Object notation (instance metho +d)
        Using this syntax is not recommended. It suffers from several serious ambiguities. See perlobj for more information. There are also sure to be several nodes around here which warn against it.

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

        I'm following up from sauoq's note on the indirect object syntax because zie didn't provide the fixed syntax (and that's all this is - syntax).

        $obj = Class->new;

        versus

        $obj = new Class;

        You can get yourself into hot water by using the second form as its ambiguous - the first is unambiguous and works all the time.

Re: CPAN Module Proposal: Business::Ship
by Aristotle (Chancellor) on Jun 01, 2003 at 12:14 UTC
    The API looks straightforward. I haven't taken the time to digest the internals, so I can't comment on that. The name is ambiguous and rather vague as is; I'd call it Business::ShippingCost, as that's its goal.

    Makeshifts last the longest.

      What kind of namespace would you recommend based on the other features I'm planning (here)
        Business::Shipping is fine then I guess. "Ship" is ambiguous - I briefly wondered what the node was going to be about before I got to it.

        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://262176]
Approved by Aristotle
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (7)
As of 2024-03-28 21:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found