Living Without Nulls
Summary
OSID Providers may not return null values. This may restrict implementors accustomed to implementing data transfer objects against databases. We need to look at the role of the OsidObject and the OsidForm as independent interfaces to solve this dilemma.
OsidObjects
OsidObjects are consumable interfaces which provide a view into a provider-owned entity. An OSID Provider may or may not provide direct administrative management of the entity. All or part of an OsidObject may be pulled from a data store, the result of data transformation, a representation of a different OsidObject, or completely made up.
In the case of a null value in a database, it is the responsibility of the OSID Provider to determine the value of a return from a mandatory method. In the OSID world, OSID Consumers get no hint from an OsidObject that a null value exists any more than OSID Consumers can understand that the OSID Provider is using a database.
So, what to do when there is no data to satisfy a mandatory method? That all depends on what it means to the OSID Provider.
Let's use osid.org.resource.ResourceRelationship as an example and try to pretend absolutely no data exists in a database used by an OSID Resource Provider. This ResourceRelationship is broken, but it demonstrates how to be compliant with the specification.
Method | Return Syntax | Return Value or Error | Comments |
---|---|---|---|
getId() | osid.id.Id | UnknownId | This is an implementation of osid.id.Id that identifies "I don't know." However, since every OsidObject is uniquely identified by its Id, there can only be one of these. One should be able to: ResourceRelationshipLookupSession.getResourceRelationship(brokenResourceRelationship.getId()) If that works, then all is well as far as the OSIDs are concerned. But you have serious design issues if you can't figure out the identifier from your data store. |
isCurrent() | boolean | false | This is connected to the nature of the implementation. Unless it is a real-time implementation where the information is current, the answer is always false. |
getRecordTypes() | osid.type.TypeList | EmptyTypeList | An osid.type.TypeList with no Types. |
hasRecordType(osid.type.Type) | boolean | false | Always false if getRecordTypes() is empty. |
getProperties() | osid.PropertyList | empty list | An osid.PropertyList with no properties. |
getPropertiesByRecordType(osid.type.Type) | osid.PropertyList | UNSUPPORTED | getRecordTypes() is empty. |
getDisplayName | osid.locale.DisplayText | EmptyDisplayText | An osid.locale.DisplayText with an empty string. |
getDescription | osid.locale.DisplayText | EmptyDisplayText | An osid.locale.DisplayText with an empty string. |
getGenusType | osid.type.Type | UnknownType | An osid.type.Type that identifies nothing. |
isOfGenusType | boolean | true if the given Type matches getGenusType() | |
isEffective() | boolean | false | |
getStartDate() | osid.calendaring.DateTime | UnknownDateTime | An implementation of osid.calendaring.DateTime that has an infinite uncertainty. We could have gone another way and implemented the concept of "always" by using an infinite granularity. In this case, isEffective() would always be true. This is an example where the lack of a value may mean two different things – "I don't know" or "forever." |
getEndDate() | osid.calendaring.DateTime | UnknownDateTime | |
hasEndReason() | boolean | false | |
getEndReasonId() | osid.id.Id | ILLEGAL_STATE | The end reason state is only available if hasEndReason is true. |
getEndReason() | osid.process.State | ILLEGAL_STATE | The end reason state is only available if hasEndReason is true. |
getSourceResourceId() | osid.id.Id | UnknownResource.getId() | We don't know what this is, so let's make up a Resource and return that. |
getSourceResource() | osid.resource.Resource | UnknownResource | |
getDestinationResourceId() | osid.id.Id | UnknownResource.getId() | |
getDestinationResource() | osid.resource.Resource | UnknownResource() | |
getResourceRelationshipRecord(osid.type.Type) | osid.resource.records.ResourceRelationshipRecord | UNSUPPORTED | getRecordTypes() is empty, so we don't support any. |
This may seem silly, but many of these Unknown and Empty implementations in the Okapia jamocha package. They are handy for stubbing things in during development.