Skip Navigation Links | |
Exit Print View | |
Remote Administration Daemon Developer Guide Oracle Solaris 11.1 Information Library |
3. Abstract Data Representation
Module Location: Deciding between Proxy or Slave
Synchronous and Asynchronous Invocation
Combining the tools described so far in this document to construct an API with a known design can be a challenge. Several possible solutions for a particular problem are often available. The examples in this section illustrate the best practices described in previous sections.
Object/interface granularity is subjective. For example, imagine an interface for managing a user. The user has a few modifiable properties:
Table 7-1 Example User Properties
|
The interface for managing this user might consist solely of a set of attributes corresponding to the above properties. Alternatively, it could consist of a single attribute that is a structure containing fields that correspond to the properties, possibly more efficient if all properties are usually read or written together. The object implementing this might be named as follows:
com.example.users:type=TheOnlyUser
If instead of managing a single user you need to manage multiple users, you have a couple of choices. One option would be to modify the interface to use methods instead of attributes, and to add a "user" argument to the methods, for example:
setUserAttributes(username, attributes) throws UserError attributes getUserAttributes(username) throws UserError
This example is sufficient for a single user, and provides support to other global operations such as adding a user, deleting a user, getting a list of users and so on. You might want to give it a more appropriate name, for example:
com.example.users:type=UserManagement
However, suppose there were many more properties associated with the user and many more operations you would want to do with a user, for example, sending them email, giving them a bonus and so on. As the server functionality grows, the UserManagement's API grows increasingly cluttered. It would accumulate a mixture of global operation and per-user operations, and the need for each per-user operation to specify a user to operate on, and specify the errors associated with not finding that user, would start looking redundant.
username[] listUsers() addUser(username, attributes) giveRaise(username, dollars) throws UserError fire(username) throws UserError sendEmail(username, message) throws UserError setUserAttributes(username, attributes) throws UserError attributes getUserAttributes(username) throws UserError
A cleaner alternative would be to separate the global operations from the user-specific operations and create two interfaces. The UserManagement object would use the global operations interface:
username[] listUsers() addUser(username, attributes)
A separate object for each user would implement the user-specific interface:
setAttributes(attributes) attributes getAttributes() giveRaise(dollars) fire() sendEmail(message)
Note that if fire operates more on the namespace than the user, it should be present in UserManagement where it would need to take a username argument.
Finally, the different objects would be named such that the different objects could be easily differentiated and be directly accessed by the client:
com.example.users:type=UserManagement com.example.users:type=User,name=ONeill com.example.users:type=User,name=Sheppard ...
This example also highlights a situation where the rad server may not want to enumerate all objects when a client issues a LIST request. Listing all users may not be particularly expensive, but pulling down a list of potentially thousands of objects on every LIST call will not benefit the majority of clients.