300 likes | 431 Views
Service Design and Compatibility. Version Compatibility. For release 1.1, Rice needs to provide middleware compatibility
E N D
Version Compatibility For release 1.1, Rice needs to provide middleware compatibility This means - client applications that are integrated with Rice middleware services need to be compatible with versions of the server that are newer then the version of Rice that the application was originally against
Version Compatibility Client Application Version 1.2 Client Application Version 1.3 Client Application Version 1.1 Rice Standalone Server Version 1.3 Arrows go both ways because in some cases server needs to communicate back with clients
Compatibility Considerations • There are two primary considerations: • Direct database access to Rice tables • Remote service calls • For the purposes of service design, we focus primarily on remote service calls • But first, in order to best position ourselves for compatibility, it is important that we adopt best practices and conventions for service design and implementation
Rethinking Service Design in Rice • There are two aspects to service design in Rice • Java service design • SOAP service design • Both of these are important, and they need to be designed in a way which makes sense for the technology • Since Rice is Java-based and we are using a code-first approach to our SOAP services, proper SOAP service design will, in part, come about naturally if we design our Java service contracts well • However, we additionally need to ensure our schema for SOAP messages is well-designed
Guiding Principles – Java Service Design Services should consist of a set of operations that are well-defined and documented They should use immutable objects for parameters and return types In many cases these immutable objects will represent “model objects” within the system Service operations will use the concrete immutable implementations of these model objects
Immutable vs. Mutable Objects • A class is immutable if the following are true: • It is “final” or has only private constructors • It’s class members are private and final • If the class has any mutable members, then they must be defensively copied when passed between the caller and the class • For Rice, we will recommend the practice of making the class final to ensure that it can’t be subclassed, as opposed to using private constructors to accomplish this
Immutable vs. Mutable Objects • Currently Rice uses mutable data transfer objects in most of it’s service APIs • There are numerous advantages to using immutable objects when design APIs • They are not susceptible to undesired external state change and can safely be cached • They are automatically thread-safe • Do not need to be copied defensively when used as a field • Have their class invariants checked once upon construction and never needs to be checked again • “Failure atomicity” – never left in inconsistent state after an exception is thrown • Can be used safely as keys in hash-based data structures
Immutable vs. Mutable and Object Copying • When using objects that can be mutated by client code you have to ensure the following: • That all mutable object parameters are “defensively copied” • That all mutable return objects are “defensively copied” • This is especially important in terms of caching! • thread safety • Not having to copy objects in the cache each time they are requested by a client is desirable for performance reasons • Also prevents “spooky action at a distance”
Construction of Immutable Objects • Immutable objects must be created in “one step” • There are three primary creational patterns for immutable objects • Constructors • Static Factory Methods • Builders
Construction of Immutable Objects • Constructors • problematic because they cannot be named • suffer from the “telescoping constructor” problem for objects with many members • Static Factory Methods • Suffer from “telescoping” signatures like constructors • But can be named, which prevents signature clashes • For rich object structures like the ones we tend to deal with in our model objects, Builders provide the most flexibility
The Builder Pattern A Builder is an object which facilitates construction of complex immutable objects It generally consists of a mutable representation of the object being built This allows for the object to be built (by invoking methods on the builder) and then the final immutable object can be created with a call to “build” At this point (or during the building of the object) the class invariants can be validated to ensure the object is constructed in a valid state Builders also handle data model expansion nicely
Recommended Creational Pattern for Rice APIs Using builders will be the recommended strategy for immutable model object construction For simpler types which represent “Parameter Objects” static factory method construction is acceptable Use of constructor creation is not recommended in the Rice APIs
Object Tiers • In order to accomplish our goals for version compatibility and improved service design, we will end up with three different types of objects that represent our service and data model • API Objects • immutable objects used on java service contracts • part of the api module • Data Transfer Objects • JAXB annotated to define their format in SOAP messages • part of the api module • Business Objects • mapped to the database using an ORM tool • mapped in the KNS Data Dictionary • part of the implementation module • Although we have three different types, API and DTO objects can generally be combined into one
Data Model Contracts • Each element of the data model could have corresponding API, DTO, and BO objects • However, as mentioned previously, in nearly all cases a single object can be used both for the API and the DTO • Each of these should implement the same data model object “contract “which is a java interface that defines accessor methods • i.e. getFirstName(), getLastName(), getId(), etc.
Model Object Design As previously mentioned, our service apis will largely use immutable “model objects” All model objects will implement the data model contract interface that defines it’s accessor methods All model objects should be Serializable All model objects should implement proper equals, hashCode, and toString The framework will provide default implementations of these which use reflection
Data Transfer Object Design DTOs will be fully JAXB annotated to support proper translation to and from SOAP messages DTOs will allow for versioning by including an annotation of type <xsd:any> which allows for data model extension in a compatible fashion Order is important - <xsd:any/> must be last element in the schema In most cases we can just add JAXB annotations to our immutable model or parameter objects
Extending the Data Model • In order to facilitate versioning of DTOs, the following rules need to be enforced: • We can never remove data elements • We can never rename data elements • We can only add optional data elements • We must define our XML schemas such that they allow for extension by declaring <xsd:any/> after all other elements
Extensible DTOs By allowing for <xsd:any> declaration in the schema, a version 1.1 XML might look like: <name> <first>John</first> <last>Doe</last> </name> If “middle” is added in version 1.2, then because extension is allowed, version 1.1 can still receive a message like the following, it will just ignore the middle namebecause it does not know about it: <name> <first>John</first> <last>Doe</last> <middle>L</middle> </name>
Business Object Design • Very little will change with the way we design BOs with the exception of: • BOs should implement the model object contract interface • BOs should provide methods for translating from and to their immutable model object counterpart • We will talk about object translation on the next slide
Translating Between Object Tiers We will often need to translate from DTOs to BOs and vice-versa To facilitate this, BO objects will provide “from” and “to” methods that handle appropriate API/DTO conversion when we need to cross tiers
Service Architecture • Java client applications will only directly use the java services provided in the api module • The implementation of these java services will load and delegate to a remote proxy if the underlying service call should go remote • If the implementation is available locally, it will invoke the service implementation • API/DTO to BO translation will occur • If the call is entering a remote service endpoint, it will delegate the call to the service implementation which will translate the DTO to a BO for internal processing
Service Versioning • Services will follow a similar set of versioning rules as data objects • Operations can never be removed or renamed • New operations can be added • If these rules ever need to be violated then we need to create a new service with a new name and continue to maintain the old service • To this end, we do not need to maintain multiple endpoints for each “version” of a service • It is recommended to re-version services after each major release, and to remove deprecated operations
Communication from Newer Server to Older Client Verisons • There are a few cases where the Rice Server needs to perform callbacks into client applications • Kim “Type” Services • Communication with client “embedded” workflow engine • Problem - clients could be on older versions of Rice! • End result is that certain clients may not support certain operations • To combat this, Rice will provide a mechanism to retrieve service “metadata” through the KSB which will include the “version” of the client service • Rice server code can then be sure to only invoke operations supported on the specific version • If server-side code fails to check first, UnsupportedOperationException is thrown when invoked
Where these Changes Will be Implemented For Rice 1.1, the main concern is for our APIs which could invoke remote services (i.e. KEW, KIM, etc.) As part of this, we will be producing general development guidelines and best practices for the code we publish as part of our public “API” So ultimately, when we work toward framework level compatibility, we should attempt to apply similar principles in our framework APIs where they make sense
The Boilerplate Problem • Boilerplate: code that is necessary for a program to work correctly but is not related to specific business problems and usually follows very specific rules. • Examples: equals, hashcode, toString, getters, setters, delegate methods • Eclipse IDE has passive code generation to make some boilerplate easier to write • leads to code rot where the code is not regenerated – rice suffers from this now!
Groovy to the Rescue • Groovy has Java-like syntax and compiles to legal java bytecode. • It has built-in facilities to generate (at compile-time) equals, hashcode, toString, getters, setters, delegate methods • Since generated at compile-time, may be an improvement over reflection-based solutions
Groovy to the Rescue (cont) Groovy code can be easily mixed with java code through cross-compiling To clients, APIs written in Groovy are the same as those written in Java Groovy has tool support for eclipse, intellij, maven, ant, etc.
Where does Groovy fit? • We feel that Groovy should be used in well defined areas – not all over rice. • We are currently targeting our BOs, DTOs, Builders, and Service Endpoints • With this approach, we can easily revert back to using straight java code without any pain to client applications. • This help minimize risk when expanding the role Groovy in the rice project