You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2017/02/14 15:13:07 UTC

[17/20] isis git commit: ISIS-1586: adds @DomainService(objectType) and rationalizes w.r.t getId()

ISIS-1586: adds @DomainService(objectType) and rationalizes w.r.t getId()

Also updates to documentation, to make clear what the precedence is for objectType (for  both domain objects and domain services).


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/5a3029eb
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/5a3029eb
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/5a3029eb

Branch: refs/heads/master
Commit: 5a3029eb04a12b851a73ebac634fb504a82aaa05
Parents: b3e9ba5
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Tue Feb 14 12:09:06 2017 +0000
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Tue Feb 14 12:09:06 2017 +0000

----------------------------------------------------------------------
 .../asciidoc/guides/_rgant-Discriminator.adoc   |  35 +++++--
 .../guides/_rgant-DomainObject_objectType.adoc  |  38 +++++--
 .../asciidoc/guides/_rgant-DomainService.adoc   |   8 ++
 .../guides/_rgant-DomainService_objectType.adoc |  58 +++++++++++
 .../guides/_rgant-PersistenceCapable.adoc       |  38 ++++---
 .../guides/_rgcms_methods_reserved_getId.adoc   |  48 ++++++++-
 .../isis/applib/annotation/DomainObject.java    |   2 +-
 .../isis/applib/annotation/DomainService.java   |  11 ++
 ...tSpecIdFacetDerivedFromClassNameFactory.java |  18 +++-
 ...vedFromDomainServiceAnnotationElseGetId.java |  32 ++++++
 .../core/metamodel/services/ServiceUtil.java    |  55 +++++++++-
 .../metamodel/services/ServiceUtil_Test.java    | 103 +++++++++++++++++++
 .../simple/dom/impl/SimpleObjectMenu.java       |   1 +
 13 files changed, 403 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgant-Discriminator.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgant-Discriminator.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgant-Discriminator.adoc
index 05ed639..9640dc2 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgant-Discriminator.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgant-Discriminator.adoc
@@ -17,15 +17,21 @@ Isis parses the `@Discriminator` annotation from the Java source code; it does n
 Moreover, while JDO/DataNucleus will recognize annotations on either the field or the getter method, Apache Isis (currently) only inspects the getter method.  Therefore ensure that the annotation is placed there.
 ====
 
-The object type is used internally by Apache Isis to generate a string representation of an objects identity (the `Oid`).  This can appear in several contexts, including:
+This value is used internally to generate a string representation of an objects identity (the `Oid`).
+This can appear in several contexts, including:
 
-* as the value of `o.a.i.applib.services.bookmark.Bookmark#getObjectType()`
-* in the `toString()` value of `Bookmark`
+* as the value of `Bookmark#getObjectType()` and in the `toString()` value of `Bookmark`
+ (see xref:rgsvc.adoc#_rgsvc_api_BookmarkService[`BookmarkService`])
+ ** and thus in the "table-of-two-halves" pattern, as per (non-ASF) http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module
+* in the serialization of `OidDto` in the xref:rgcms.adoc#_rgcms_schema-cmd[command] and xref:rgcms.adoc#_rgcms_schema-ixn[interaction] schemas
 * in the URLs of the xref:ugvro.adoc#[RestfulObjects viewer]
 * in the URLs of the xref:ugvw.adoc#[Wicket viewer] (in general and in particular if xref:ugvw.adoc#_ugvw_features_hints-and-copy-url[copying URLs])
 * in XML snapshots generated by the xref:rgsvc.adoc#_rgsvc_api_XmlSnapshotService[`XmlSnapshotService`]
 
 
+
+== Examples
+
 For example:
 
 [source,java]
@@ -38,21 +44,30 @@ public class Customer {
 
 has an object type of `custmgmt.Customer`.
 
-If the object type has not been specified, then Apache Isis will use the fully qualified class name of the entity.
 
+== Precedence
 
+The rules of precedence for determining a domain object's object type are:
 
-[TIP]
-====
-This might be obvious, but to make explicit: we recommend that you use namespaces of some form or other.
+1. xref:rgant.adoc#_rgant_Discriminator[`@Discriminator`]
+2. `@DomainObject#objectType`
+3. xref:rgant.adoc#_rgant_PersistenceCapable[`@PersistenceCapable`], if at least the `schema` attribute is defined.  +
++
+If both `schema` and `table` are defined, then the value is "`schema.table`".
+If only `schema` is defined, then the value is "`schema.className`".
 
-If the object type has not been specified (by either this annotation, or by xref:rgant.adoc#_rgant-PersistenceCapable[`@PersistenceCapable`], or by Apache Isis' own xref:rgant.adoc#_rgant-DomainObject_objectType[`@DomainObject#objectType()`] annotation/attribute), then (as noted above) Apache Isis will use the fully qualified class name of the entity.
+4. Fully qualified class name of the entity.
 
-However, chances are that the fully qualified class name is liable to change over time, for example if the code is refactored or (more fundamentally) if your company/organization reorganizes/renames itself/is acquired.
 
-We therefore strongly recommend that you specify an object type for all entities, one way or another.  Specifying `@Discriminator` will override `@PersistenceCapable`, which overrides `@DomainObject#objectType()`.  Using `@PersistenceCapable#schema()` is probably the best choice in most cases.
+[TIP]
+====
+This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain objects.
+
+Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the object will break.
+At best this will require a data migration in the database; at worst it could cause external clients accessing data through the xref:ugvro.adoc#[Restful Objects] viewer to break.
 ====
 
+
 [NOTE]
 ====
 If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.  An error message will be printed in the log to help you determine which classes have duplicate object tyoes.

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainObject_objectType.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainObject_objectType.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainObject_objectType.adoc
index aa1ccc1..c081f0d 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainObject_objectType.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainObject_objectType.adoc
@@ -7,40 +7,58 @@
 
 The `objectType()` attribute is used to provide a unique alias for the object's class name.
 
-This value is used internally to generate a string representation of an objects identity (the `Oid`).  This can appear in several contexts, including:
+This value is used internally to generate a string representation of an objects identity (the `Oid`).
+This can appear in several contexts, including:
 
-* as the value of `o.a.i.applib.services.bookmark.Bookmark#getObjectType()`
-* in the `toString()` value of `Bookmark`
+* as the value of `Bookmark#getObjectType()` and in the `toString()` value of `Bookmark`
+ (see xref:rgsvc.adoc#_rgsvc_api_BookmarkService[`BookmarkService`])
+ ** and thus in the "table-of-two-halves" pattern, as per (non-ASF) http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module
+* in the serialization of `OidDto` in the xref:rgcms.adoc#_rgcms_schema-cmd[command] and xref:rgcms.adoc#_rgcms_schema-ixn[interaction] schemas
 * in the URLs of the xref:ugvro.adoc#[RestfulObjects viewer]
 * in the URLs of the xref:ugvw.adoc#[Wicket viewer] (in general and in particular if xref:ugvw.adoc#_ugvw_features_hints-and-copy-url[copying URLs])
 * in XML snapshots generated by the xref:rgsvc.adoc#_rgsvc_api_XmlSnapshotService[`XmlSnapshotService`]
 
 
+
+== Examples
+
 For example:
 
 [source,java]
 ----
 @DomainObject(
-    objectType="ORD"
+    objectType="orders.Order"
 )
 public class Order {
     ...
 }
 ----
 
-If the object type has not been specified, then Apache Isis will use the fully qualified class name of the entity.
+
+== Precedence
+
+The rules of precedence are:
+
+1. xref:rgant.adoc#_rgant_Discriminator[`@Discriminator`]
+2. `@DomainObject#objectType`
+3. xref:rgant.adoc#_rgant_PersistenceCapable[`@PersistenceCapable`], if at least the `schema` attribute is defined.  +
++
+If both `schema` and `table` are defined, then the value is "`schema.table`".
+If only `schema` is defined, then the value is "`schema.className`".
+
+4. Fully qualified class name of the entity.
 
 
 [TIP]
 ====
-This might be obvious, but to make explicit: we recommend that you use namespaces of some form or other for object types.
-
-As noted above, if the object type has not been specified, then Apache Isis will use the fully qualified class name of the entity.  However, this is liable to change over time, for example if the code is refactored or (more fundamentally) if your company/organization reorganizes/renames itself/is acquired.
+This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain objects.
 
-We therefore strongly recommend that you specify an object type for all entities, either using `objectType()` or using the JDO xref:rgant.adoc#_rgant-PersistenceCapable[`@PersistenceCapable`] (with a `schema()` attribute) or xref:rgant.adoc#_rgant-Discriminator[`@Discriminator`] annotations.  Specifying `@Discriminator` will override `@PersistenceCapable`, which in turn overrides `objectType()`.  Using `@PersistenceCapable#schema()` is probably the best choice in most cases.
+Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the object will break.
+At best this will require a data migration in the database; at worst it could cause external clients accessing data through the xref:ugvro.adoc#[Restful Objects] viewer to break.
 ====
 
 [NOTE]
 ====
-If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.  An error message will be printed in the log to help you determine which classes have duplicate object tyoes.
+If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.
+An error message will be printed in the log to help you determine which classes have duplicate object tyoes.
 ====

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService.adoc
index 8dd1ec4..8e76d37 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService.adoc
@@ -27,6 +27,13 @@ The table below summarizes the annotation's attributes.
 |the nature of this service: providing actions for menus, or as contributed actions, or for the xref:ugvro.adoc#[RestfulObjects REST API], or neither
 
 
+|xref:rgant.adoc#_rgant-DomainService_objectType[`objectType()`]
+|
+|(`1.14.0-SNAPSHOT`) equivalent to xref:rgant.adoc#_rgant_DomainObject_objectType[`@DomainObject#objectType()`], specifies the objectType of the service.
+
+The instanceId for services is always "1".
+
+
 |xref:rgant.adoc#_rgant-DomainService_repositoryFor[`repositoryFor()`]
 |
 |if this domain service acts as a repository for an entity type, specify that entity type.  This is used to determine an icon to use for the service (eg as shown in action prompts).
@@ -57,6 +64,7 @@ public class LoanRepository {
 
 
 include::_rgant-DomainService_nature.adoc[leveloffset=+1]
+include::_rgant-DomainService_objectType.adoc[leveloffset=+1]
 include::_rgant-DomainService_repositoryFor.adoc[leveloffset=+1]
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService_objectType.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService_objectType.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService_objectType.adoc
new file mode 100644
index 0000000..0432b54
--- /dev/null
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgant-DomainService_objectType.adoc
@@ -0,0 +1,58 @@
+[[_rgant-DomainService_objectType]]
+= `objectType()` (`1.14.0-SNAPSHOT`)
+:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+:_basedir: ../
+:_imagesdir: images/
+
+
+The `objectType()` attribute is used to provide a unique alias for the domain service's class name.
+
+This value is used internally to generate a string representation of an service identity (the `Oid`).
+This can appear in several contexts, including:
+
+* as the value of `Bookmark#getObjectType()` and in the `toString()` value of `Bookmark`
+ (see xref:rgsvc.adoc#_rgsvc_api_BookmarkService[`BookmarkService`])
+* in the serialization of `OidDto` in the xref:rgcms.adoc#_rgcms_schema-cmd[command] and xref:rgcms.adoc#_rgcms_schema-ixn[interaction] schemas
+* in the URLs of the xref:ugvro.adoc#[RestfulObjects viewer]
+* in the URLs of the xref:ugvw.adoc#[Wicket viewer] (specifically, for bookmarked actions)
+
+
+
+== Example
+
+For example:
+
+[source,java]
+----
+@DomainService(
+    objectType="orders.OrderMenu"
+)
+public class OrderMenu {
+    ...
+}
+----
+
+
+
+== Precedence
+
+The rules of precedence are:
+
+1. `@DomainService#objectType`
+2. xref:rgcms.adoc#_rgcms_methods_reserved_getId[`getId()`]
+3. The fully qualified class name.
+
+
+[TIP]
+====
+This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain services.
+
+Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the service will break.
+At best this will require a data migration in the database; at worst it could cause external clients accessing data through the xref:ugvro.adoc#[Restful Objects] viewer to break.
+====
+
+[NOTE]
+====
+If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.
+An error message will be printed in the log to help you determine which classes have duplicate object tyoes.
+====

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgant-PersistenceCapable.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgant-PersistenceCapable.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgant-PersistenceCapable.adoc
index c755915..a1d01bd 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgant-PersistenceCapable.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgant-PersistenceCapable.adoc
@@ -18,16 +18,23 @@ Moreover, while JDO/DataNucleus will recognize annotations on either the field o
 ====
 
 
-The object type is used internally by Apache Isis to generate a string representation of an objects identity (the `Oid`).  This can appear in several contexts, including:
+This value is used internally to generate a string representation of an objects identity (the `Oid`).
+This can appear in several contexts, including:
 
-* as the value of `o.a.i.applib.services.bookmark.Bookmark#getObjectType()`
-* in the `toString()` value of `Bookmark`
+* as the value of `Bookmark#getObjectType()` and in the `toString()` value of `Bookmark`
+ (see xref:rgsvc.adoc#_rgsvc_api_BookmarkService[`BookmarkService`])
+ ** and thus in the "table-of-two-halves" pattern, as per (non-ASF) http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module
+* in the serialization of `OidDto` in the xref:rgcms.adoc#_rgcms_schema-cmd[command] and xref:rgcms.adoc#_rgcms_schema-ixn[interaction] schemas
 * in the URLs of the xref:ugvro.adoc#[RestfulObjects viewer]
 * in the URLs of the xref:ugvw.adoc#[Wicket viewer] (in general and in particular if xref:ugvw.adoc#_ugvw_features_hints-and-copy-url[copying URLs])
 * in XML snapshots generated by the xref:rgsvc.adoc#_rgsvc_api_XmlSnapshotService[`XmlSnapshotService`]
 
 
-The actual format of the object type used by Apache Isis for the concatenation of `schema()` and `@PersistenceCapable#table()`.  If the `table()` is not present, then the class' simple name is used instead.
+The actual format of the object type used by Apache Isis for the concatenation of `schema()` and `@PersistenceCapable#table()`.
+If the `table()` is not present, then the class' simple name is used instead.
+
+
+== Examples
 
 For example:
 
@@ -64,26 +71,33 @@ public class AddressImpl {
 
 does _not_ correspond to an object type, because the `schema()` attribute is missing.
 
-If the object type has not been specified, then Apache Isis will use the fully qualified class name of the entity.
 
 
+== Precedence
 
-[TIP]
-====
-This might be obvious, but to make explicit: we recommend that you use namespaces of some form or other.
+The rules of precedence for determining a domain object's object type are:
 
-If the object type has not been specified (by either this annotation, or by xref:rgant.adoc#_rgant-Discriminator[`@Discriminator`], or by Apache Isis' own xref:rgant.adoc#_rgant-ObjectType[`@DomainObject#objectType()`] annotation/attribute), then (as noted above) Apache Isis will use the fully qualified class name of the entity.
+1. xref:rgant.adoc#_rgant_Discriminator[`@Discriminator`]
+2. `@DomainObject#objectType`
+3. xref:rgant.adoc#_rgant_PersistenceCapable[`@PersistenceCapable`], if at least the `schema` attribute is defined.  +
++
+If both `schema` and `table` are defined, then the value is "`schema.table`".
+If only `schema` is defined, then the value is "`schema.className`".
 
+4. Fully qualified class name of the entity.
 
-However, chances are that the fully qualified class name is liable to change over time, for example if the code is refactored or (more fundamentally) if your company/organization reorganizes/renames itself/is acquired.
 
-Isis' recognition of `@PersistenceCapable#schema()` makes namespacing of object types comparatively trivial, and moreover aligns the namespacing with the way in which the tables in the database are namespaced by the database schema.
+[TIP]
+====
+This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain objects.
 
-We therefore strongly recommend that you specify an object type for all entities, one way or another.  Using `@PersistenceCapable#schema()` is probably the best choice in most cases.
+Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the object will break.
+At best this will require a data migration in the database; at worst it could cause external clients accessing data through the xref:ugvro.adoc#[Restful Objects] viewer to break.
 ====
 
 
 
+
 [NOTE]
 ====
 If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.  An error message will be printed in the log to help you determine which classes have duplicate object tyoes.

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/adocs/documentation/src/main/asciidoc/guides/_rgcms_methods_reserved_getId.adoc
----------------------------------------------------------------------
diff --git a/adocs/documentation/src/main/asciidoc/guides/_rgcms_methods_reserved_getId.adoc b/adocs/documentation/src/main/asciidoc/guides/_rgcms_methods_reserved_getId.adoc
index 3c37320..ef1dbe1 100644
--- a/adocs/documentation/src/main/asciidoc/guides/_rgcms_methods_reserved_getId.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/_rgcms_methods_reserved_getId.adoc
@@ -5,15 +5,53 @@
 :_imagesdir: images/
 
 
-The `getId()` method applies only to domain services, and allows a unique identifer to be provided for that service.
+The `getId()` method applies only to domain services, and is used to provide a unique alias for the domain service's class name.
 
-This identifier corresponds in many ways to the xref:rgant.adoc#_rgant-DomainObject_objectType[`objectType()`] attribute for domain objects; it is used as an internal identifier but also appears in URLs within the xref:ugvro.adoc#[RestfulObjects viewer]'s REST API.
+This value is used internally to generate a string representation of an service identity (the `Oid`).
+This can appear in several contexts, including:
+
+* as the value of `Bookmark#getObjectType()` and in the `toString()` value of `Bookmark`
+ (see xref:rgsvc.adoc#_rgsvc_api_BookmarkService[`BookmarkService`])
+* in the serialization of `OidDto` in the xref:rgcms.adoc#_rgcms_schema-cmd[command] and xref:rgcms.adoc#_rgcms_schema-ixn[interaction] schemas
+* in the URLs of the xref:ugvro.adoc#[RestfulObjects viewer]
+* in the URLs of the xref:ugvw.adoc#[Wicket viewer] (specifically, for bookmarked actions)
+
+
+
+== Example
+
+For example:
+
+[source,java]
+----
+@DomainService
+public class OrderMenu {
+    ...
+    public String getId() { return "orders.OrderMenu"; }
+}
+----
+
+
+
+== Precedence
+
+The rules of precedence are:
+
+1. xref:rgant.adoc#_rgant_DomainService_objectType[`@DomainService#objectType()`]
+2. xref:rgcms.adoc#_rgcms_methods_reserved_getId[`getId()`]
+3. The fully qualified class name.
 
-If the identifier is omitted, the services fully qualified class name is used.
 
 [TIP]
 ====
-Unlike domain objects, where the use of an object type is strongly encouraged (eg using xref:rgant.adoc#_rgant-PersistenceCapable[`@PersistenceCapable`]), it matters much less if an id is specified for domain services.  The principle benefit is shorter URLs in the REST API.
-====
+This might be obvious, but to make explicit: we recommend that you always specify an object type for your domain services.
 
+Otherwise, if you refactor your code (change class name or move package), then any externally held references to the OID of the service will break.
+At best this will require a data migration in the database; at worst it could cause external clients accessing data through the xref:ugvro.adoc#[Restful Objects] viewer to break.
+====
 
+[NOTE]
+====
+If the object type is not unique across all domain classes then the framework will fail-fast and fail to boot.
+An error message will be printed in the log to help you determine which classes have duplicate object tyoes.
+====

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainObject.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainObject.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainObject.java
index e653d78..97a9512 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainObject.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainObject.java
@@ -137,7 +137,7 @@ public @interface DomainObject {
 
 
     /**
-     * Provides a unique abbreviation for the object type, eg &quot;CUS&quot; for Customer.
+     * Provides a unique abbreviation for the object type, eg &quot;customer.Customer&quot; for Customer.
      *
      * <p>
      * This value, if specified, is used in the serialized form of the object's OID.  An OID is

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainService.java
----------------------------------------------------------------------
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainService.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainService.java
index 1c2c1ea..16dfe54 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/DomainService.java
@@ -33,6 +33,17 @@ import java.lang.annotation.*;
 @Retention(RetentionPolicy.RUNTIME)
 public @interface DomainService {
 
+
+
+    /**
+     * Provides the (first part of the) unique identifier (OID) for the service (the instanceId is always &quot;1&quot;).
+     *
+     * <p>
+     * If not specified then either the optional &quot;getId()&quot is used, otherwise the class' name.
+     */
+    String objectType() default "";
+
+
     /**
      * If this domain service acts as a repository for an entity type, specify that entity type.
      */

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
index 6fccfcd..29b011e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromClassNameFactory.java
@@ -56,7 +56,23 @@ public class ObjectSpecIdFacetDerivedFromClassNameFactory extends FacetFactoryAb
         }
         final Class<?> originalClass = processClassContaxt.getCls();
         final Class<?> substitutedClass = classSubstitutor.getClass(originalClass);
-        FacetUtil.addFacet(new ObjectSpecIdFacetDerivedFromClassName(substitutedClass.getCanonicalName(), facetHolder));
+
+        final boolean isService = isService(facetHolder);
+        ObjectSpecIdFacet objectSpecIdFacet = isService
+                ? new ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId(substitutedClass, facetHolder)
+                : new ObjectSpecIdFacetDerivedFromClassName(substitutedClass.getCanonicalName(), facetHolder);
+        FacetUtil.addFacet(objectSpecIdFacet);
+    }
+
+    private boolean isService(final FacetHolder facetHolder) {
+        boolean service;
+        if(facetHolder instanceof ObjectSpecification) {
+            ObjectSpecification objectSpecification = (ObjectSpecification) facetHolder;
+            service = objectSpecification.isService();
+        } else {
+            service = false;
+        }
+        return service;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId.java
new file mode 100644
index 0000000..651a765
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/objectspecid/classname/ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId.java
@@ -0,0 +1,32 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.objectspecid.classname;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacetAbstract;
+import org.apache.isis.core.metamodel.services.ServiceUtil;
+
+public class ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId extends ObjectSpecIdFacetAbstract {
+
+    public ObjectSpecIdFacetDerivedFromDomainServiceAnnotationElseGetId(final Class<?> value, final FacetHolder holder) {
+        super(ServiceUtil.id(value), holder);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServiceUtil.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServiceUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServiceUtil.java
index 0fc6991..956aadc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServiceUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/ServiceUtil.java
@@ -22,6 +22,9 @@ package org.apache.isis.core.metamodel.services;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
+import com.google.common.base.Strings;
+
+import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.core.commons.exceptions.IsisException;
 
 public final class ServiceUtil {
@@ -29,16 +32,58 @@ public final class ServiceUtil {
     private ServiceUtil() {
     }
 
+    public static String id(final Class<?> serviceClass) {
+        final String serviceType = serviceTypeOf(serviceClass);
+        if (serviceType != null) {
+            return serviceType;
+        }
+
+        try {
+            Object object = serviceClass.newInstance();
+            return getIdOf(object);
+        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException e) {
+            return fqcnOf(serviceClass);
+        }
+    }
+
     public static String id(final Object object) {
-        final Class<?> cls = object.getClass();
+        final Class<?> serviceClass = object.getClass();
+        final String serviceType = serviceTypeOf(serviceClass);
+        if(serviceType != null) {
+            return serviceType;
+        }
+
         try {
-            final Method m = cls.getMethod("getId");
+            return getIdOf(object);
+        } catch (final NoSuchMethodException e) {
+            return fqcnOf(serviceClass);
+        }
+    }
+
+    private static String serviceTypeOf(final Class<?> serviceClass) {
+        final String serviceType;
+        final DomainService domainService = serviceClass.getAnnotation(DomainService.class);
+        if(domainService != null) {
+            serviceType = domainService.objectType();
+            if(!Strings.isNullOrEmpty(serviceType)) {
+                return serviceType;
+            }
+        }
+        return null;
+    }
+
+    private static String getIdOf(final Object object) throws NoSuchMethodException {
+        try {
+            final Class<?> objectClass = object.getClass();
+            final Method m = objectClass.getMethod("getId");
             return (String) m.invoke(object);
         } catch (final SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
             throw new IsisException(e);
-        } catch (final NoSuchMethodException e) {
-            final String id = object.getClass().getName();
-            return id.substring(id.lastIndexOf('.') + 1);
         }
     }
+
+    private static String fqcnOf(final Class<?> serviceClass) {
+        return serviceClass.getName();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/ServiceUtil_Test.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/ServiceUtil_Test.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/ServiceUtil_Test.java
new file mode 100644
index 0000000..895f879
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/ServiceUtil_Test.java
@@ -0,0 +1,103 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.services;
+
+import org.junit.Test;
+
+import org.apache.isis.applib.annotation.DomainService;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class ServiceUtil_Test {
+
+    @DomainService(objectType = "foo.SomeServiceAnnotated")
+    public static class SomeServiceAnnotated {}
+
+
+    @DomainService()
+    public static class SomeServiceWithId {
+
+        public String getId() {
+            return "bar.SomeServiceWithId";
+        }
+    }
+
+
+    @DomainService(objectType = "bop.SomeServiceAnnotated")
+    public static class SomeServiceAnnotatedAndWithId {
+
+        public String getId() {
+            return "bop.SomeServiceWithId";
+        }
+    }
+
+    @DomainService()
+    public static class SomeServiceWithoutAnnotationOrId {
+
+    }
+
+    @Test
+    public void annotated() throws Exception {
+
+        assertThat(
+                ServiceUtil.id(new SomeServiceAnnotated()),
+                is(equalTo("foo.SomeServiceAnnotated")));
+
+        assertThat(
+                ServiceUtil.id(SomeServiceAnnotated.class),
+                is(equalTo("foo.SomeServiceAnnotated")));
+    }
+
+    @Test
+    public void id() throws Exception {
+
+        assertThat(
+                ServiceUtil.id(new SomeServiceWithId()),
+                is(equalTo("bar.SomeServiceWithId")));
+
+        assertThat(
+                ServiceUtil.id(SomeServiceWithId.class),
+                is(equalTo("bar.SomeServiceWithId")));
+    }
+
+    @Test
+    public void annotated_precedence_over_id() throws Exception {
+
+        assertThat(
+                ServiceUtil.id(new SomeServiceAnnotatedAndWithId()),
+                is(equalTo("bop.SomeServiceAnnotated")));
+
+        assertThat(
+                ServiceUtil.id(SomeServiceAnnotatedAndWithId.class),
+                is(equalTo("bop.SomeServiceAnnotated")));
+    }
+
+    @Test
+    public void fallback_to_fqcn() throws Exception {
+        assertThat(
+                ServiceUtil.id(new SomeServiceWithoutAnnotationOrId()),
+                is(equalTo("org.apache.isis.core.metamodel.services.ServiceUtil_Test$SomeServiceWithoutAnnotationOrId")));
+        assertThat(
+                ServiceUtil.id(SomeServiceWithoutAnnotationOrId.class),
+                is(equalTo("org.apache.isis.core.metamodel.services.ServiceUtil_Test$SomeServiceWithoutAnnotationOrId")));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/5a3029eb/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjectMenu.java
----------------------------------------------------------------------
diff --git a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjectMenu.java b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjectMenu.java
index b3e7ea4..63a03a0 100644
--- a/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjectMenu.java
+++ b/example/application/simpleapp/module-simple/src/main/java/domainapp/modules/simple/dom/impl/SimpleObjectMenu.java
@@ -41,6 +41,7 @@ import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
 )
 public class SimpleObjectMenu {
 
+    public String getId() { return "simpleObjects"; }
 
     @Action(semantics = SemanticsOf.SAFE)
     @ActionLayout(bookmarking = BookmarkPolicy.AS_ROOT)