Introduction
The Network Service Definition Language is used to specify the message exchange between client / server applications. The language specifies the messages, their parameters, message direction, timeout, authorization, delivery style, custom types, constants, and documentation for the service and each message, parameter, type, and constant. A compiler can then be used to process the NSDL into meta-data and code to support implementing clients and servers in a variety of different programming languages and environments with the help of a small runtime library.
Syntax Elements
The NSDL uses syntax elements common to other programming languages:
- Keywords are used to identify grammar elements such as service, constants, enumerations, structured types, external types, exceptions, and native types. The keywords are: boolean, byte, const, double, enum, exception, extends, extern, false, float, int, long, mixin, include, module, null, object, service, short, string, struct, throws, true, and void. Keywords may not be used as an identifier for any service defined element.
- Boolean, numeric, and string literals are used to specify the values of constants, and as arguments to annotations. Examples include true, 49, 3.14159, and "boo". The usual hexadecimal, octal, and binary integral formats (e.g., 0x23, 023, and 0b011 respectively) are also supported. The usual string escapes as well as the Unicode escape are supported:
- Identifiers are used to specify the name of the service, each message, parameter, custom type, and constant. An identifier is composed of ASCII characters, may start with an upper or lowercase alphabetic, and may contain upper and lower case alphabetic, numeric, or underscore characters. While not enforced (yet), the recommended style is that service, enum, struct, exception, and externnames should start with uppercase and use camel case conventions (e.g., FastCar, BlackCatWalking, etc.). Message and parameter names should start with lowercase and use camel case conventions (e.g., makeCall, sendName, etc.). Constants and enum elements should be all uppercase with underscores (e.g., FISH_SOUP, STALE_BREAD, etc.).
- Definitions may be terminated by a semi-colon ( ";" ) but that is totally optional.
- Comments may be the standard Java types:
- // causes the rest of the line to be ignored.
- /* blah */ causes blah to be ignored; may extend over multiple lines; may not be nested.
- /** blah */ is a formal comment. Adds a description to the following definition (if allowed). Follows Javadoc style. Descriptions are copied into the generated interfaces.
Native Data Types
The native types include:
- boolean - true or false
- byte - signed 8-bit quantity (-2^7 to 2^7-1)
- short - signed 16-bit quantity (-2^15 to 2^15-1)
- int - signed 32-bit quantity (-2^31 to 2^31-1)
- long - signed 64-bit quantity (-2^63 to 2^63-1)
- float - single precision IEEE floating point (max 3.4028235e38)
- double - double precision IEEE floating point (max 1.7976931348623157e308)
- string - sequence of characters (generally, Unicode), arbitrary length
These types correspond to the architectural types found on almost all computer systems.
Extended Types
Common extended types are also included.
Datetime
Datetime represents a specific moment in time.
Examples
List
A List represents an ordered collection of values.
Examples
Set
A Set represents an unordered collection of unique values.
Examples
Map
A Map represents a set of keys, each of which points to a value.
Example
Object Data Type
An object is used to model a parameter or result where the actual type is determined at runtime. An object type specification may be used anywhere a native data type is allowed except when declaring constants.
Examples
Constants
A constant declaration is used to include named values into the generated interfaces. There is no messaging component associated with them, just a handy way to define a named value which is available to each implementation in appropriate target syntax.
The value of the constant must be appropriate for the type.
Constant names are by convention all uppercase, with underscore used to separate words.
Syntax
Examples
Enumerations
An enumeration is used to declare a type which has a named set of values. These are most often used where the set of values is small. Some languages use integers to model enumerations while others use tokens (identifiers) and yet others use object instances.
Enumeration names are by convention capitalized. Enumeration item names are all uppercase.
Syntax
Examples
Structured Type
A structure is used to model a small set of named parameters of heterogeneous type. When a structure contains another structure as a parameter it is by reference. Thus, in Etch, a structure may contain itself (directly or indirectly) as a parameter (if the containment was by value, a structure could not contain itself because that would introduce a storage recursion).
Structure names are by convention capitalized. Parameter names are by convention lowercase.
Strangely enough, a structure might not have any parameters. This might seem silly, but the mere presence of the structure might be used like a boolean to assert a condition (that is, indicate the condition is true, whereas null indicates the condition is false), while allowing for future expansion by adding parameters to the structure.
Note that <ptype> includes the native types as well as <id>, <qid>, and <object>. The <id> or <qid> is used to indicate a custom type name. An <object> is used to model a parameter or result where the actual type is determined at runtime.
A structure may extend another structure. In this way the structure inherits the parameters and identity of the extended structure. This is functionally equivalent to class inheritance in java, csharp, ruby, etc.
There are some rules:
- a structure may not extend another such that an inheritance loop is created.
- a structure may not declare a parameter whose name is the same as any already in its inheritance.
Syntax
Examples
Arrays
As you can see in <param>, above, a parameter in a structure may be dimensioned. The number of square bracket pairs indicates the dimensionality. None indicates a scalar parameter. There is no particular limit on the number of dimensions you may have or the size of any particular dimension. Let your common sense guide you, keeping in mind all the possible language bindings there might be.
Examples
External Type
Sometimes you really need to be able to reference a target language specific type in the NSDL. When you do, you must think carefully about the applicability to other language bindings that may want or need to use your service. Many useful types have analogs in the other languages anyway, such as Java's HashMap, ArrayList, and Date types. Note that Etch cannot model abstract or generic types, and so where abstracts or generics are involved they must be made concrete. In Java, StrIntMap instead of HashMap<String, Integer>, HashMap, or even Map:
When you specify the external type, you import it into Etch by giving it a name that you will use inside Etch to reference it. Then for each language binding, you supply information about what the concrete type is and how to serialize / de-serialize it.
Note: the @Extern annotation, along with all the other annotations, is described later. It is only shown here because otherwise the example of extern would be too vague. See the appendix for an extern example.
Syntax
Examples
Exceptions
Exceptions are a special kind of struct which are an alternative response to a message execution. An exception cannot be a parameter or result of a message, nor a parameter in a structure.
When a program wants to return an exception in Java, it creates an instance of the exception and throws it (compare this with creating an instance of a normal type and returning it). The throw operation, unlike return, can be performed deep in the call chain and terminates each call up the chain until caught by a handler. Return, on the other hand, can only terminate one call up the call chain and makes its value available to the next level up.
In some programming languages (e.g., Java) exceptions are checked: a method cannot throw a checked exception unless it has declared that it might. Unchecked exceptions can always be thrown, and usually correspond to low-level conditions such as an attempt to use a null pointer, index an array element which doesn't exist, etc. In other programming languages (e.g., C#, Ruby, Python), all exceptions are unchecked. Some programming languages (e.g., C) do not have a concept of exception whatsoever.
Etch reflects this by requiring methods to declare thrown exceptions. In this way the declarations may be passed through to programming languages which require the declarations. Of course, it is good for the users of a service to know which exceptions a particular message might throw.
Note: exceptions should include at least some detail as to the cause, enough to suggest a solution of the problem. In messaging systems this is especially important, as a client often does not have enough access to the server to debug a problem from the server side. As you might expect, an exception with a simple message "parameter x (value 23) is not in the expected range (1-10)" is way more helpful than "value out of range". Furthermore, localization considerations might dictate that you declare an exception like this:
and let the receiver format it as they wish. (But, that can get messy really fast!)
Like structures, exception may extend other exceptions. See the discussion under structures,above.
Syntax
Examples
Messages
Messages describe the actual network traffic between the clients and servers. We use function definition syntax but the elements are the same: a message type and parameters (names and types).
Message declarations actually describe two messages (if the message is two-way). The first is the request message which includes the message type and parameters with argument values. The second is the response message which includes the response message type and the result parameter and value.
Messages can terminate normally by returning a value or they can terminate abnormally by throwing an exception. Either type of termination will return a response message from the remote process (again, unless the message is declared one-way).
NOTE: The vast majority of modern programming languages support exceptions; those that do not will need to accommodate the dual nature of the response message.
NOTE: One-way messages must have a void result type and not throw any exceptions.
Message names and parameter names by convention start with a lowercase alphabetic, then use camel case.
Syntax
Examples
Includes
You many add an include to to an etch servicde function.
Syntax
Example
To use include files in an etch file, use the .txt extention, not .etch for includes.
You can also include other include files in the etch include files.
testincl.txt
Mixin
A mixin is the mechanism used to incorporate the messages and types defined within another service into the current service. To the extent possible the original service retains its identity, such that with an object oriented language such as Java, C#, or Ruby, an instance of the current service could be passed where an instance of the mixed in service is expected. This facilitates the creation of reusable service libraries. You can have as many mixins as you like (up to the limit imposed by the various language bindings).
Two examples of a mixin are an authentication service and an event service. These could be standard components for a deployment of a suite of other services. You only have to define them once, then you can add them to each of your other services as you like.
Syntax
Examples
NestedMixin
Important Notes
- The mixin etch file must be present in the Include Path. In the above example, NestedMixin must be present in the Include Path. If the include path is not specified, then the Mixin etch file will only be searched in the current directory. Thus in the above example, if -I option (include option) is not specified, then NestedMixin will only be searched in the directory in which MixinTest is present.
- When a user compiles the etch file which contains the mixin (TestMixin.etch in the above example), he can chose whether he wants the mixin artifacts to be generated or not. If he chooses not to generate the artifacts, then he must specify -n command line option If he chooses to generate the mixin artifacts then he must specify either -m or -d option (Please refer to the Etch Compiler Guide for more details on these options). Failure to specify either one of these option will lead to compilation problem.
Service
All the definitions above are packaged into a service, which provides a naming scope. The compiler uses the features available in each language binding to create the naming scope. For example, in Java, the compiler creates a number of interfaces and classes with names derived from the service name.
Syntax
Example
Annotations
Annotations may be applied to a <stmt> to alter its behavior. Where it makes sense, you can even apply an annotation to the <service> to set the global behavior or defaults. See below for descriptions of supported annotations.
Syntax
Examples
Module
The last bit of syntax is the module. This appears in the NSDL file before the service and defines the global namespace of the service. This has a very strong correlation to the Java package or the C# namespace, and a looser correlation to the Ruby module. It also contributes to the fully qualified name of the service itself, which is used to distinguish the service from other like-named services.
Syntax
Examples
Supported Annotations
@AsyncReceiver
Specifies that the processing mode used by the server for this message or the default processing mode for all the messages of the service. A specification on a message overrides the service default.
- NONErequests the message be delivered on the message receiver thread. While this message is being processed, no other message may be read and delivered. Messages are processed in the order received.
- QUEUEDrequests the message be queued to a thread pool for delivery. This is generally used by operations which complete relatively quickly but might want to dialog with the caller using callbacks, or perform some other operation which might briefly block.
- FREErequests the message be delivered by a newly created free thread. This is generally used by long running operations.
Default
The service default is NONE.
May Be Applied To
<message_stmt>, <service>
Example
@Authorize
Specifies that the message receiver should use the specified method to determine whether the message is allowed to be processed, or set the service default authorization method for all messages in the service. Method should be a service message declared the same direction as this message and returning a boolean result. An exception will be thrown by the receiver if the message is not allowed. A specification on a message overrides the service default.
The args are a sequence of named or unnamed constants, enumeration values, or message parameters or parameters of parameters, etc., corresponding in number, order, and type to the signature of method.
Method true always allows the message, while falsenever allows the message. Neither of these are allowed any args.
Default
The service default is true. If specified without an argument, true is assumed.
May Be Applied To
<message_stmt>, <service>
Example
@Direction
Specifies the direction of the message, or sets the service default direction for all messages in the service:
- SERVER means from the client to the server.
- CLIENT means from the server to the client (a callback).
- BOTH means either client or server may send the message to the other.
A specification on a message overrides the service default.
Default
The service default is SERVER.
May Be Applied To
<message_stmt>, <service>
Example
@Extern
Declares language binding and serializer for an external type. Language is a compiler supported name for a binding (e.g., java, csharp, ruby); name is the language specific name of the type to use; nImport is a string that must be used in something like an import or requires statement to make name available; serializer is the language specific name of the type to use to import and export (this type must declare certain specific methods); sImport is a string that must be used in something like an import, using, include, or require statement to make serializer available.
Default
There is no default. If NSDL is compiled for a language which has no binding for an external type, the compilation will fail.
May Be Applied To
<extern_stmt>
Example
@Oneway
Specifies that the sender of this message does not wait for any response, or specifies the service default for all messages. This is sometimes called a fire-and-forget message, and some other times called an event message. Such a message must be declared to have a void result, and may not be declared to throw any exceptions. A specification on a message overrides the service default.
Default
The service default is false. If specified without an argument, true is assumed.
May Be Applied To
<message_stmt>, <service>
Example
@Timeout
Specifies the time in milliseconds for the sender of a message to wait for a response, or specifies the service default timeout for all messages. Timeout of '0' means wait forever. A specification on a message overrides the service default.
Default
The service default is .
May Be Applied To
<message_stmt>, <service>
Example
@ToString
Specifies a format specifier for language specific "to string" methods. The "to string" methods are written in the appropriate way for the target languages. Why "to string" methods? They are needed for debugging and logging.
Default
The default is "param1={param1}, ..." for each parameter of the struct or exception.
May Be Applied To
<struct_stmt>, <except_stmt>
Example
@Unchecked
In languages with checked exceptions, specifies than an exception should be unchecked, or specifies that the service default for exceptions should be unchecked.
Default
The service default is false. If specified without an argument, true is assumed.
May Be Applied To
<except_stmt>, <service>
Example
Formal Comments
Formal comments are used to describe each Etch service description element. The descriptions are copied over into the generated files so that they are available to the programmers in their natural environments. Here is a simple example:
This produces (for the java target) the following output:
A formal comment may be attached to:
- Service
- Constant
- Enumeration
- Enumeration element
- Structure
- Exception
- Message
- Parameter of a structure, exception, or message
- Exception thrown by a message
The formal comment is started by the /* sequence, and terminated by the */ sequence. The contents, minus internal sequences that look like <eol><whitespace><whitespace> are attached to the element in question.
A parameter of a structure, exception, or message may be commented two ways. The Javadoc style was shown in the HydroMath example above. The second style is shown here:
The body of the formal comment is taken whole for elements except for a structure, exception, or message. For those the body is scanned looking for @param tags, and for a message, @return or @throws tags. If / when one of those tags is found, the main description is terminated and text is spooled to the description of the sub-element. In all cases the descriptions may span multiple lines.
@param <name> <descr>
The name must specify the name of a structure, exception, or message.
@return <descr>
The message must be defined to return a value.
@throws <name> <descr>
The name must specify the name of an exception that the message throws. |