usr/src/doc/rad-dev/c-concepts-apis.xml
author Stephen Talley <stephen.talley@oracle.com>
Tue, 19 Jun 2012 17:37:39 -0400
changeset 865 025928ac8888
parent 842 abc3d63bd4da
permissions -rw-r--r--
rad dev guide changes for api<->interface rename

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<!--
  PDL HEADER START

  Public Documentation License Notice

  The contents of this Documentation are subject to the Public
  Documentation License Version 1.01 (the "License"); you may only
  use this Documentation if you comply with the terms of this License.
  A copy of the License is available at
  http://www.opensolaris.org/os/community/documentation/license.

  PDL HEADER END

  Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
-->

<section><title>Interfaces</title>

<para>
The primary purpose of <command>rad</command> is to consistently expose
the various pieces of the system for administration.  Not all
subsystems are alike, however: each has a data and state model tuned to
the problems they are solving.  Though there are major benefits to
using a common model across components when possible, uniformity comes
with trade-offs.  The increased inefficiency and client complexity
&mdash; and risk of decreased developer adoption &mdash; often warrant
using an interface designed for problem at hand.
</para>

<para>
An interface is a formal definition of how a client may
interact with a <command>rad</command> server object.  An
interface may be shared amongst several objects, e.g. when
maintaining a degree of uniformity is possible and useful, or may be
implemented by only one.  It is analogous to an interface or pure
abstract class in an object oriented programming language.  In the case
of <command>rad</command>, an interface consists of a
name, the set of features a client may interact with, optionally a set
of derived types referenced by the features, and a version.  The
features supported include methods (procedure calls made in the context
of a specific object), properties (which are functionally equivalent to
methods but bear different semantics), and asynchronous event sources.
</para>

    <section><title>Name</title>
    <para>
    Each interface has a name.  This name is used by the
    toolchain to construct indentifier names when generating code, and
    is returned by the server along with the rest of the
    interface definition when an object is examined by a
    client.  There isn't a global namespace for
    interfaces, however.  A client is expected either to
    know which objects implement which interfaces (typical
    consumer) or to query <command>rad</command> for the object's full
    interface definition (debugger or interactive tools).
    </para>

    <para>
    Though in the future <command>rad</command> may establish a
    namespace for interfaces, the lack of a namespace
    doesn't present a serious problem today.  A <trademark
    class='trade'>Unix</trademark> executable isn't going to open every
    library in <filename class='directory'>/usr/lib</filename> until it
    finds one that claims to offer the <quote>libc</quote> interface;
    it is going to open <filename
    class='libraryfile'>/usr/lib/libc.so.1</filename>.  Likewise, the
    typical client isn't going to search for objects claiming to export
    a particular interface, it will be acting on the
    specific object documented to have not just the desired
    interface, but the desired behavior as well.
    </para>
    </section>

    <section><title>Derived Types</title>
    <para>
    Three classes of derived types may be defined for use by features:
    <type>structure</type>s, <type>union</type>s, and 
    <type>enumeration</type>s.  Each type defined must be uniquely 
    named.  As with interfaces, there isn't a global type 
    namespace.  The types defined in an <acronym>API</acronym> are only 
    available to the features defined in that <acronym>API</acronym>.
    </para>
    </section>

    <section><title>Features</title>
    <para>
    The only thing all three feature types &mdash; methods, attributes,
    and events &mdash; have in common is that they are named.  All
    three feature types' names exist in the same namespace, and must
    therefore be unique.  i.e. it is not possible to have both a method
    and an attribute called <quote>foo</quote>.  This exclusion is
    intended as a simple mechanism to avoid the majority of conflicts
    that could arise when trying to naturally map these interface
    features to a client environment.
    </para>

    <note>
    <para>
    Enforcing a common namespace for interface features
    isn't always enough.  Some language environments place additional
    constraints on naming.  For instance, a <acronym>JMX</acronym>
    client using MXBean proxies will see an interface with
    synthetic methods of the form <function>getXXX()</function> or
    <function>setXXX()</function> for accessing attribute XXX that must
    coexist with other method names &mdash; explicitly defining methods
    with those names will cause a conflict.  Unfortunately, at the
    moment, it is up to the developer to be aware of these issues.  In
    the future such situations may be identified by the toolchain and a
    warning issued.
    </para>
    <para>
    One alternative would be to escape method and attribute names in
    the <acronym>JMX</acronym> client.  By prefixing every method name
    with <quote>M</quote> and every property with <quote>P</quote>, for
    example, overlaps between the synthetic and real methods would be
    eliminated.  Each approach has its trade-offs.
    </para>
    </note>

    <section><title>Methods</title>
    <para>
    A method is a procedure call made in the context of the object it
    is called on.  In addition to a name, a method
    <emphasis>may</emphasis> define a return type, can define
    <emphasis>zero or more</emphasis> arguments, and
    <emphasis>may</emphasis> declare that it returns an error,
    optionally with an error return type.
    </para>

    <para>
    If a method does not define a return type, it returns no value.  It
    is effectively of type <type>void</type>.  If a method defines a
    return type, and that type is permitted to be nullable (see
    previous chapter), the return value may be defined to be nullable.
    </para>

    <para>
    Each method argument has a name and a type.  If any argument's type
    is permitted to be nullable (see previous chapter) that argument
    may be defined to be nullable.
    </para>

    <para>
    If a method doesn't declare that it returns an error, it
    theoretically cannot fail.  No system is infallible, though.
    Because the connection to <command>rad</command> could be broken
    (either due to a network problem or a catastrophic failure in
    <command>rad</command> itself), <emphasis>all</emphasis> method
    calls can fail with an I/O error.  If a method declares that it
    returns an error but does not specify a type, the method may fail
    due to <acronym>API</acronym>-specific reasons.  Clients will
    distinguish this failure type from I/O failures.  Finally, if a
    method also defines an error return type, data of that type may be
    provided to the client in the case where the
    <acronym>API</acronym>-specific failure occurs.  Error payloads are
    implicitly optional, and must therefore be of a type that is
    permitted to be nullable.
    </para>
    
    <para>
    Methods names may not be overloaded.
    </para>

    </section>

    <section><title>Attributes</title>
    <para>
    An attribute is metaphorically a property of the object.  In
    addition to a name, each attribute has a type, is defined to be one
    of read-only, read-write, or write-only, and (like a method)
    <emphasis>may</emphasis> declare that accessing the attribute
    returns an error, optionally with an error return type.
    </para>

    <para>
    Reading a read-only or read-write attribute returns the value of
    that attribute.  Writing a write-only or read-write attribute sets
    the value of that attribute.  It is invalid to read a write-only
    attribute or write a read-only attribute.  Clients may treat
    attempts to write to a read-only attribute as a write to an
    attribute that doesn't exist.  Likewise, attempts to read from a
    write-only attribute may be treated as an attempt to read from an
    attribute that doesn't exist.
    </para>

    <para>
    If an attribute's type is permitted to be nullable, its value may
    be defined to be nullable.
    </para>

    <para>
    An attribute may optionally declare that it returns an error, with
    the same semantics as declaring (or not declaring) an error for a
    method.  Unlike a method, an attribute may have different error
    declarations for reading the attribute and writing the attribute.
    </para>

    <para>
    Attribute names may not be overloaded.  To be clear, it is not
    valid to define a read-only attribute and a write-only attribute
    with the same name.
    </para>

    <para>
    Given methods, attributes are arguably a superfluous
    interface feature.  Writing an attribute of type
    <type>X</type> can be implemented with a method that takes one
    argument of type <type>X</type> and returns nothing, and reading an
    attribute of type <type>X</type> can be implemented with a method
    that takes no arguments and returns a value of type
    <type>X</type>.  Attributes are included because they have slightly
    different semantics, a fact that is recognized by many language
    environments.  In particular, an explicit attribute mechanism:

    <itemizedlist>
    <listitem><para>
	Enforces symmetric access for reading and writing read-write
	attributes.
    </para></listitem>
    <listitem><para>
	Can be easily and automatically translated to a form
	natural to the client language-environment.
    </para></listitem>
    <listitem><para>
	Communicates more about the nature of the interaction.  Though
	it isn't mandatory, it is recommended that reading an attribute
	not affect system state.  It is also recommended that the value
	written to a read-write attribute be the value returned on
	subsequent reads (unless there is an intervening change to the
	system that effectively <quote>writes</quote> a new value).
    </para></listitem>
    </itemizedlist>
    </para>
    </section>

    <section><title>Events</title>
    <para>
    An event is an asynchronous notification generated by rad and
    consumed by clients.  A client may subscribe to events by name to
    register interest in them.  The subscription is performed on an
    object which implements an interface. In addition to a name, each event
    has a type.
    </para>
    <para>
    Events have the following characteristics:
    <itemizedlist>
    <listitem><para>
	Sequential.
    </para></listitem>
    <listitem><para>
	Volatile
    </para></listitem>
    <listitem><para>
	Guaranteed
    </para></listitem>
    </itemizedlist>
    </para>
    <para>
    A client can rely on sequential delivery of events from a server
    as long as the connection to the server is maintained. If the
    connection fails, then events will be lost. On re-connection, a
    client must re-subscribe to resume the flow of events.
    </para>
    <para>
    Once a client has subscribed to an event, event notifications will
    be received until the client unsubscribes from the event.
    </para>
    <para>
    On receipt of a subscribed event, a client receives a payload of
    the defined type.
    </para>
    </section>

    </section>

    <section><title>Version</title>

    <para>
    <acronym>API</acronym>s change over time.  Sometimes a change to an
    <acronym>API</acronym> is incompatible with existing consumers.
    Other times a change is compatible with existing consumers, but may
    breed a new batch of consumers that are unable to use the
    <acronym>API</acronym> that was in place before the change
    occurred.  Moreover, not all features are created equal.  Some
    represent committed interfaces whose compatibility is paramount,
    others are private interfaces that are changed only in lockstep
    with the software that uses them.  To address all these issues,
    <command>rad</command> interfaces are versioned.
    </para>

    <section><title>Numbering</title>
    <para>
    Measuring the compatibility of a change is the first problem that
    needs to be solved.  <command>rad</command> uses a simple
    <replaceable>major</replaceable>.<replaceable>minor</replaceable>
    versioning scheme.  When a compatible change to an
    interface is made, its minor version number is
    incremented.  When an incompatible change is made, its major
    version number is incremented and its minor version number is reset
    to 0.
    </para>

    <para>
    In other words, an implementation of an interface that claims to be
    version <replaceable>X</replaceable>.<replaceable>Y</replaceable>
    (where <replaceable>X</replaceable> is the major version and
    <replaceable>Y</replaceable> is the minor version) must support any
    client expecting version
    <replaceable>X</replaceable>.<replaceable>Z</replaceable>, where
    <replaceable>Z</replaceable> &lt;= <replaceable>Y</replaceable>.
    </para>

    <para>
    The following interface changes are considered
    compatible:

    <itemizedlist>
    <listitem><para>
        Adding a new event.
    </para></listitem>
    <listitem><para>
        Adding a new method.
    </para></listitem>
    <listitem><para>
        Adding a new attribute.
    </para></listitem>
    <listitem><para>
	Expanding the access supported by an attribute (e.g. from
	read-only to read-write).
    </para></listitem>
    <listitem><para>
	A change from nullable to non-nullable for a method 
	return value or	readable property (i.e. decreasing the range 
	of a feature).
    </para></listitem>
    <listitem><para>
	A change from non-nullable to nullable for a method 
	argument or writable property (i.e. increasing the domain 
	of a feature).
    </para></listitem>
    </itemizedlist>
    </para>

    <para>
    The following interface changes are considered
    incompatible:

    <itemizedlist>
    <listitem><para>
        Removing an event.
    </para></listitem>
    <listitem><para>
        Removing a method.
    </para></listitem>
    <listitem><para>
        Removing an attribute.
    </para></listitem>
    <listitem><para>
	Changing the type of an attribute, method, or event.
    </para></listitem>
    <listitem><para>
	Changing a type definition referenced by an attribute, method,
	or event.
    </para></listitem>
    <listitem><para>
	Decreasing the access supported by an attribute (e.g. from
	read-write to read-only).
    </para></listitem>
    <listitem><para>
        Adding or removing method arguments.
    </para></listitem>
    <listitem><para>
	A change from non-nullable to nullable for a method 
	return value or	readable property (i.e. increasing the range 
	of a feature).
    </para></listitem>
    <listitem><para>
	A change from nullable to non-nullable for a method 
	argument or writable property (i.e. decreasing the domain 
	of a feature).
    </para></listitem>
    </itemizedlist>
    </para>

    <note>
    <para>
    An interface is more than just a set of methods,
    attributes, and events.  Associated with those features are
    well-defined behaviors.  If those behaviors change, even if the
    structure of the interface remains the same, a change
    to the version number may be required to honor the above
    guarantee.
    </para>
    </note>

    </section>

    <section><title>Commitment</title>
    <para>
    To solve the problem wherein different features are intended for
    different consumers, <command>rad</command> defines three
    commitment levels: private, uncommitted, and committed.  Each
    method, attribute, and event in an interface defines
    its commitment level independently.  The interface is
    assigned a separate version number, as described in the previous
    section, per commitment level.
    </para>

    <para>
    Each commitment level is considered a superset of the next
    more-committed level.  e.g. <quote>private</quote> is a superset of
    <quote>uncommitted</quote>.  This means then when an uncommitted
    (or committed) interface changes, the private version number needs
    to be changed as well.  By having separate version numbers (instead
    of just adding more dots to the existing one), private/uncommitted
    consumers aren't broken by compatible changes to
    uncommitted/committed interfaces.
    </para>

    <para>
    When a feature changes commitment level, it is treated as if the
    feature was removed from the old commitment level and added to the
    new one.  If a feature becomes less committed, then that implies an
    incompatible change for every commitment level that no longer
    includes that feature, but no change for every commitment level
    that still includes the feature (due to the implicit nesting of
    commitment levels).  If a feature becomes more committed, then that
    implies a compatible change for each commitment level that gained
    the feature, and no change for each commitment level that had it
    before.
    </para>

    <para>
    In the simple case of an <acronym>API</acronym>containing
    interfaces of only a single commitment level, this reduces to
    traditional commitment-agnostic major/minor versioning.
    </para>
    </section>

    <section><title>Clients and Versioning</title>

    <para>
    A <command>rad</command> client can ask for interface
    version information (if the protocol in use doesn't automatically
    provide it).  What the client does with this information is
    entirely up to it.  It can expose that information directly to
    interface consumers, or it can provide <acronym>API</acronym>s 
    that encapsulate
    verifying that the version the client expects is the version the
    server is providing.
    </para>

    <para>
    In the future, it is conceivable that changes to the
    <command>rad</command> server would result in relaxing what
    constitutes an incompatible change.  For instance, additional
    nullable method arguments or carefully constrained derived type
    modifications could both be allowed without impacting consumers.
    </para>
    </section>
    </section>

    <!--

    <section><title>Example</title>
    <para>
    <classsynopsis class="class">
    <oointerface><interfacename>UserManagement</interfacename></oointerface>

    <fieldsynopsis>
    <modifier>RO</modifier>
    <modifier>nullable</modifier>
    <type>UserData[]</type><varname>userlist</varname>
    </fieldsynopsis>
    <fieldsynopsis>
    <modifier>WO</modifier>
    <type>password</type><varname>root_password</varname>
    </fieldsynopsis>
    <fieldsynopsis>
    <modifier>RW</modifier>
    <type>string</type><varname>default_shell</varname>
    </fieldsynopsis>

    <methodsynopsis>
    <type>void</type>
    <methodname>createUser</methodname>
    <methodparam>
    <type>string</type><parameter>name</parameter>
    </methodparam>
    <methodparam>
    <modifier>nullable</modifier>
    <type>string</type><parameter>dataset</parameter>
    </methodparam>
    <exceptionname>UserEx</exceptionname>
    </methodsynopsis>

    <fieldsynopsis>
    <modifier>Event</modifier>
    <type>string</type><varname>newusers</varname>
    </fieldsynopsis>

    </classsynopsis>
    </para>
    </section>
    -->

</section>