Basic Design rules for CLM perl module.

  1. Will not use fancy interface/implementation stuff in this go around.
    This means simply that all objects are straightforward concrete imps. No special behind the scenes loaders like DBD/DBI. And the objects will be simple, for sample there will be a WorkItem object only, not a Story that inherits from Task which inherits from WorkItem, etc.

  2. Only external (not part of basic install) dependency will be XML::Simple
    We need SOME sort of XML parser (and converter) so we will use XML::Simple because it is simple. No JSON converters. Trying to keep it to just XML::Simple and the LWP and HTTP modules.

  3. Methods will use explicit get and set prefix to indicate what is happening.
    Call me old. Yes, it is possible to make the implementation be sensitive to one-arg / no-arg conventions and either set (one-arg) or get (no-arg) a thing. But the context is valid in the developer's head for only about 60 minutes after which time even he forgets what the code is really trying to do. And the next guy who has to read it has it even worse. At 1AM in the morning, you simply want to find setTitle, not title and then look at the code around it to see what's actually going on -- especially with the syntactic liberties one can take with perl!

  4. Essentially all URI RDF resource hell will be encapsulated
    Apps will deal with objects and variables that are directly related to data that is "expected" and appropriate and recognizable. For example, instead of exposing contributor in a hashref like this:
        $w->{"dcterms:contributor"} = { 'rdf:resource' => 'https://machine:port/jts/users/_763467823' };
    
    we will provide methods like this:
        $task->setContributor("d159308");
    
    This extends to things like estimates and corrrected estimates, which will be get/set using some sort of a reasonable thing, not milliseconds (i.e. 180000 is 3 mins!).

    We will also do our best to hide many enumerations and typenames like com.ibm.team.workitem.taskWorkflow.

    And last but not least, tricky bits like changing state of a workitem from New to In Progress will be well-encapsulated within the workitem object, not left as a dangerous safari into the world of the special ?_action= arg.

  5. Project Areas are assumed to be containers of acceptable enums, workflow states, and so forth. Thus, objects will be vended from project areas, not created "naked":
        my $task = new CLM2::WorkItem();  # No!
        $task->setState("foo");  # Is foo acceptable as a state in the project area
        # we eventually INTEND to write?  Unknown!
    
        my $cmpa = $clm->getCMProjectArea("Test Project Area");
    
        my $task = $cmpa->vendWorkItem("task");  # Yes!
    
        #  $task is properly set up "on the inside" to be saved into the 
        #  "Test Project Area."
    

  6. Keys to items in the CLM datastore are not the same as objects vended by the CLM module and shall be kept separate.
    In all the OSLC RESTful docs and examples, curl, the lightweight REST client, and other means are used to GET and POST content. That's great except what is missing is state. The objects in CLM represent state and data independent of the CLM datastore. There's a whole lot of "assume a JSON file exists and post it thusly" and other snippets that entirely skip how these activities actually occur in code. And code typically does more than just post a thing. There are loops, subroutines, comparisons, etc.

    The "traditional and simple" way to handle something like this would be to have a hashref of info that would be processed and then a NEW hashref return of material coming back from the web service, e.g.

        $w->{"dcterms:contributor"} = { 'rdf:resource' => 'https://machine:port/jts/users/_763467823' };
        $w->{"dcterms:title"} = { "A Title" };
    
        $result = CLMsave($w);
    
        if($result->ok()) {
            print "new key: ", $result->{content}->{'dcterms:identifier'};
        }
        
        #  Or, if you were a little more fancy about it:
        my $task = new CLM2::Task();
        $task->setTitle("A title");
        $task->setContribute("me");
        if($clm->save($task) == 0) {
            print "new key: ", $task->getKey() ;   # Ahhh... This is BAD part...
        }
    
    The reason getKey() is bad is because the key is not a property of the task object; it is a key into the CLM datastore. What happens if we do this:
        my $task = new CLM2::Task();
        $task->getKey();  # huh?  not yet set!
    
    Even more important: the app code CANNOT set the ID on a task, ie.. $task->setKey(). Why? Because only the CLM datastore is permitted to do so! But the setKey() must exist somewhere in the stack, so... what do we do? Make a public-side/imp-side set of classes to hide certain methods that can only be called by the imp? Nah! Total overkill.

    Instead, we will crisply separate workitems, tasks, requirements, etc. as objects independent of the keys in the CLM datastore:

        my $task = $cmpa->vendWorkItem("task");
        $task->setTitle("a title");
    
        #  At this point, the $task object exists in memory only!  There is no
        #  id, not resource URI, nothing.
    
        my $key = $cmpa->create($task);
    
        #  At this point, the $task object data exists in the CLM datastore and
        #  is referenced by the $key.  $key is an abstract object representing 
        #  one item in the CLM datastore.  It is NOT the same as $task.
    
    The $task is not the $key and indeed -- the $task does not even CONTAIN a key. You cannot query by $task, nor can you call setTitle() on an $key. This separation of concerns allows us to do the following: A consequence to this design is that you have to be careful about shipping around the object without the key or visa versa:
        my $task = $cmpa->vendTask();
        my $key = $cmpa->create($task);
    
        doSomething($key);
        # unless doSomething calls fetch() on $key there is not going to be
        # much you can do.
    
        doOther($task);
        # You won't be able to update this task in doOther() as it exists
        # under $key because there's no way to get the key from the object!
    
    It is anticipated that script writers needing to deal with sets of objects and related keys will have to construct their own means of dealing with that; here is one possible solution:
        my $z = {};
    
        for $k (qw/10404 10565/) {  # Assume 10404 and 10565 are valid workitem ids...
    
            #  More on query() in just a bit but for now....
            my ($key, $tsk) = $cmpa->query( { id => $k } );
    
            $z->{$key} = $tsk; # ah HA!  Exploit stringy-ness of perl hashrefs...
        }
    
        doSomething($cmpa, $z);   # Ah!  $z carries everything we need!
    
        sub doSomething {
            my($cmpa, $info) = @_;
    
            for $key (keys %{$info}) {   # key to hash is full fledged $id object!
                my $task = $z->{$key};
                # Modify the title as an example:
                $task->setTitle( $task->getTitle() . " -- and more!" ) ;
                my $rc = $cmpa->update($key, $task);
            }
        }
    

  7. The query() function will return pairs of keys and objects.
    The simpler fetch() function will return a single object given a key:
        my $workItem = $cmpa->fetch($key); # assume we had $key from somewhere
    
    This is simple and straightforward. But arbitrary query is not, for two important reasons:
    1. The query might return more than one matching result.
    2. The query parameters do not need to be keys but can be attributes of the object
    Now, this combined with the design feature of separating keys from objects means that if you query for something that returns, say, 3 valid things, you have to capture both the key and object for all three things. This leads to the following perl-friendly query syntax:
        # General syntax:
        my @resultPairs = $cmpa->query( various kinds of params );
    
        # Most basic use:  a hashref on input; values to keys assumed to be
        # operator "equals" and implied AND across keys.  In this example,
        # we see id, which is the actual identifier as can be "seen" by a
        # user of the system (as opposed to the internal key, usually a 
        # wretched URI...
        my ($key, $task) = $cmpa->query( { id => '10565' } ); # we assume one here!
    
        # This is the juice:
        my @resultPairs = $cmpa->query( { ownedBy => 'd159308' } ); 
        # @resultPairs will be ($key1, $task1, $key2, $task2, $key3, $task3, ...)
    
        # One way to work with this material:
        for $key (@resultPairs) {
            my $task = shift @resultPairs;
    
            # now you can operate on $key, $task;
        }
    

  8. Project Areas will be considered specific for a particular kind of function, e.g. task/CM, requirements, testing, etc.
        my $cmpa = $clm->getCMProjectArea("Test Project Area");
        my $qmpa = $clm->getQMProjectArea("A quality place");
        my $rmpa = $clm->getRMProjectArea("What do you require?");
    
    We could get fancy with this but not at this junction. Yes, yes, we should have a way to generically deal with project areas but a CM project area cannot vend testCases and RM cannot vendWorkItems so that's that.