Adding guidelines for types upgrade

This commit is contained in:
Luca Frosini 2024-06-24 14:20:31 +02:00
parent 2d9c91f226
commit 2b8f7a7820
1 changed files with 580 additions and 0 deletions

View File

@ -0,0 +1,580 @@
.. _update_type_definition:
How to update a type definition
================================
This guide provides a concrete example of how to update a type definition while respecting backwards compatibility.
Imagine you have defined the following facet:
.. code:: java
import java.util.Date;
import org.gcube.com.fasterxml.jackson.annotation.JsonFormat;
import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.gcube.informationsystem.base.reference.Element;
import org.gcube.informationsystem.model.reference.entities.Facet;
import org.gcube.informationsystem.types.annotations.ISProperty;
import org.gcube.informationsystem.types.reference.Change;
import org.gcube.informationsystem.types.reference.TypeMetadata;
import org.gcube.informationsystem.utils.Version;
import org.gcube.resourcemanagement.model.impl.entities.facets.EventFacetImpl;
/**
* EventFacet captures information on a certain event/happening characterising the life cycle of the resource.
*
* Examples of an event are the start time of a virtual machine or the activation time of an electronic service.
*
* https://wiki.gcube-system.org/gcube/GCube_Model#Event_Facet
*
* @author Luca Frosini (ISTI - CNR)
*/
@JsonDeserialize(as=EventFacetImpl.class)
@TypeMetadata(
name = EventFacet.NAME,
description = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
+ "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
version = Version.MINIMAL_VERSION_STRING
)
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
public interface EventFacet extends Facet {
public static final String NAME = "EventFacet"; // EventFacet.class.getSimpleName();
@JsonFormat(shape= JsonFormat.Shape.STRING, pattern = Element.DATETIME_PATTERN)
@ISProperty(description = "The time the event took place/occurred", mandatory=true, nullable=false)
public Date getDate();
public void setDate(Date date);
@ISProperty(description = "The typology of event", mandatory=true, nullable=false)
public String getEvent();
public void setEvent(String event);
}
which produces the following type definition:
.. code:: javascript
{
"type": "FacetType",
"id": "b76de0ac-048a-43a0-ae4f-e3fdb1a7d27c",
"name": "EventFacet",
"description": "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
"properties":
[
{
"type": "PropertyDefinition",
"name": "date",
"description": "The time the event took place/occurred",
"mandatory": true,
"readonly": false,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "Date"
},
{
"type": "PropertyDefinition",
"name": "event",
"description": "The typology of event",
"mandatory": true,
"readonly": false,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
}
],
"version": "1.0.0",
"changelog": { "1.0.0": "First Version" },
"abstract": false,
"final": false,
"extendedTypes": [ "Facet" ]
}
At a certain point, the information-system-model introduces the following Property type, which models an event.
.. code:: java
import java.util.Date;
import org.gcube.com.fasterxml.jackson.annotation.JsonFormat;
import org.gcube.com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.gcube.informationsystem.base.reference.Element;
import org.gcube.informationsystem.model.impl.properties.EventImpl;
import org.gcube.informationsystem.types.annotations.Final;
import org.gcube.informationsystem.types.annotations.ISProperty;
import org.gcube.informationsystem.types.reference.Change;
import org.gcube.informationsystem.types.reference.TypeMetadata;
import org.gcube.informationsystem.utils.Version;
/**
* @author Luca Frosini (ISTI - CNR)
*/
@JsonDeserialize(as=EventImpl.class)
@TypeMetadata(name = Event.NAME, description = "This type provides information regarding an event using the Five Ws (checklist) https://en.wikipedia.org/wiki/Five_Ws", version = Version.MINIMAL_VERSION_STRING)
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
@Final
public interface Event extends Property {
public static final String NAME = "Event"; // Event.class.getSimpleName();
public static final String WHAT_PROPERTY = "what";
public static final String WHEN_PROPERTY = "when";
public static final String WHO_PROPERTY = "who";
public static final String WHERE_PROPERTY = "where";
public static final String WHY_PROPERTY = "why";
public static final String HOW_PROPERTY = "how";
@ISProperty(name = WHAT_PROPERTY, description = "WHAT happened.", readonly = true, mandatory = true, nullable = false)
public String getWhat();
public void setWhat(String what);
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = Element.DATETIME_PATTERN)
@ISProperty(name = WHEN_PROPERTY, description = "WHEN the event occured. It is represented in the format " + Element.DATETIME_PATTERN + ".", readonly = true, mandatory = true, nullable = false)
public Date getWhen();
public void setWhen(Date when);
@ISProperty(name = WHO_PROPERTY, description = "WHO triggered the event.", readonly = true, mandatory = false, nullable = false)
public String getWho();
public void setWho(String who);
@ISProperty(name = WHERE_PROPERTY, description = "The location (can be virtual) WHERE the event occurred.", readonly = true, mandatory = false, nullable = false)
public String getWhere();
public void setWhere(String where);
@ISProperty(name = WHY_PROPERTY, description = "The reason WHY the event occurred.", readonly = true, mandatory = false, nullable = false)
public String getWhy();
public void setWhy(String why);
@ISProperty(name = HOW_PROPERTY, description = "How the event occurred.", readonly = true, mandatory = false, nullable = false)
public String getHow();
public void setHow(String how);
}
which produces the following type definition:
.. code:: javascript
{
"type": "PropertyType",
"id" : "",
"name": "Event",
"description": "This type provides information regarding an event using the Five Ws (checklist) https://en.wikipedia.org/wiki/Five_Ws",
"properties":
[
{
"type": "PropertyDefinition",
"name": "how",
"description": "How the event occurred.",
"mandatory": false,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
},
{
"type": "PropertyDefinition",
"name": "what",
"description": "WHAT happened.",
"mandatory": true,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
},
{
"type": "PropertyDefinition",
"name": "when",
"description": "WHEN the event occured. It is represented in the format yyyy-MM-dd HH:mm:ss.SSS Z.",
"mandatory": true,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "Date"
},
{
"type": "PropertyDefinition",
"name": "where",
"description": "The location (can be virtual) WHERE the event occurred.",
"mandatory": false,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
},
{
"type": "PropertyDefinition",
"name": "who",
"description": "WHO triggered the event.",
"mandatory": false,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
},
{
"type": "PropertyDefinition",
"name": "why",
"description": "The reason WHY the event occurred.",
"mandatory": false,
"readonly": true,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "String"
}
],
"version": "1.0.0",
"changelog": { "1.0.0": "First Version" },
"abstract": false,
"final": true,
"extendedTypes": [ "Property" ]
}
So imagine you want to update the 'EventFacet' type definition to use this property type instead of the 'date' and 'event' properties.
To update the 'EventFacet' there are different strategies:
Java Backwards Compatibility
----------------------------
This solution is Java compliant from a code perspective.
Moreover, instances using the old format are properly deserialised.
.. code:: java
/**
* EventFacet captures information on a certain event/happening characterising the life cycle of the resource.
*
* Examples of an event are the start time of a virtual machine or the activation time of an electronic service.
*
* https://wiki.gcube-system.org/gcube/GCube_Model#Event_Facet
*
* @author Luca Frosini (ISTI - CNR)
*/
@JsonDeserialize(as=EventFacetImpl.class)
@TypeMetadata(
name = EventFacet.NAME,
description = "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. "
+ "Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
version = EventFacet.VERSION
)
@Changelog ({
@Change(version = EventFacet.VERSION, description = "Switching to {@link Event} Property added in the information-system-model"),
@Change(version = Version.MINIMAL_VERSION_STRING, description = Version.MINIMAL_VERSION_DESCRIPTION)
})
public interface EventFacet extends Facet {
public static final String NAME = "EventFacet"; // EventFacet.class.getSimpleName();
public static final String VERSION = "2.0.0";
public static final String EVENT_PROPERTY = "event";
@Deprecated
/**
* This method will be removed soon.
* Use {@link EventFacet#getTheEvent()} method which return {@link Event} instance.
* The date can be retrieved by using {@link Event#getWhen()} method.
*
* @return the date of the occurred event.
*/
public Date getDate();
@Deprecated
/**
* This method will be removed soon.
* Use {@link EventFacet#setEvent(Event event)}.
* The date can be set in {@link Event} instance by using
* {@link Event#setWhen(Date date)} method.
*
* @param date the date of the occurred event.
* which is the same of the 'when' Date of the {@link Event} instance.
*/
public void setDate(Date date);
@Deprecated
/**
* This method will be removed soon.
* Use {@link EventFacet#getTheEvent()} method which return {@link Event} instance.
* The deprecated 'event' String can be retrieved by using {@link Event#getWhat()} method.
*
* @return the string of event, e.g. active
*/
public String getEvent();
@Deprecated
/**
* This method will be removed soon.
* Use {@link EventFacet#setEvent(Event event)}.
* The deprecated 'event' String can be set in {@link Event} instance by using
* {@link Event#setWhat(String what)} method.
*
* @param event the string of the event to set, e.g. failed
* which is the same of the 'what' String of the {@link Event} instance.
*/
public void setEvent(String event);
/**
* Actually is not mandatory for backward compatibility.
* In the next version this method will be deprecated to and it will be replaced
* by a method with the following signature
*
* public Event getTheEvent()
*
* This is not actually possible because we need to maintain
* {@link EventFacet#getEvent()} for backward compatibility
*
* @return the event
*/
@ISProperty(name=EVENT_PROPERTY, type=Object.class, description = "The event eventually described by the five W (What, When, Who, Where, Why)", mandatory=false, nullable=false)
public Event getTheEvent();
/**
* Set the event
* @param event the event to set
*/
@JsonSetter(EVENT_PROPERTY)
public void setEvent(Event event);
}
which produces the following type definition:
.. code:: javascript
{
"type": "FacetType",
"id": "b76de0ac-048a-43a0-ae4f-e3fdb1a7d27c",
"name": "EventFacet",
"description": "EventFacet captures information on a certain event/happening characterising the life cycle of the resource. Examples of an event are the start time of a virtual machine or the activation time of an electronic service.",
"properties": [
{
"type": "PropertyDefinition",
"name": "event",
"description": "The event eventually described by the five W (What, When, Who, Where, Why)",
"mandatory": false,
"readonly": false,
"notnull": true,
"max": null,
"min": null,
"regexp": null,
"defaultValue": null,
"propertyType": "Any"
}
],
"version": "2.0.0",
"changelog": {
"2.0.0": "Switching to {@link Event} Property added in the information-system-model",
"1.0.0": "First Version"
},
"abstract": false,
"final": false,
"extendedTypes": [ "Facet" ]
}
The implementation should be something like this:
.. code:: java
import java.util.Date;
import java.util.LinkedHashMap;
import org.gcube.com.fasterxml.jackson.annotation.JsonGetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonIgnore;
import org.gcube.com.fasterxml.jackson.annotation.JsonSetter;
import org.gcube.com.fasterxml.jackson.annotation.JsonTypeName;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.informationsystem.model.impl.entities.FacetImpl;
import org.gcube.informationsystem.model.impl.properties.EventImpl;
import org.gcube.informationsystem.model.reference.properties.Event;
import org.gcube.informationsystem.serialization.ElementMapper;
import org.gcube.resourcemanagement.model.reference.entities.facets.EventFacet;
/**
* @author Luca Frosini (ISTI - CNR)
*/
@JsonTypeName(value = EventFacet.NAME)
public class EventFacetImpl extends FacetImpl implements EventFacet {
/**
* Generated Serial version UID
*/
private static final long serialVersionUID = -4130548762073254058L;
protected Event event;
@Override
@Deprecated
/**
* {@inheritDoc}
*
* This function is actually a shortcut to
* {@link EventFacet#getTheEvent()}.{@link Event#getWhen()}
*/
@JsonIgnore
public Date getDate() {
if(event==null) {
return null;
}
return event.getWhen();
}
@Override
@Deprecated
/**
* {@inheritDoc}
*
* This function is actually a shortcut to
* {@link EventFacet#getTheEvent()}.{@link Event#setWhen(Date date)}
*
* It eventually instantiate {@link Event} if null.
*/
@JsonSetter("date")
public void setDate(Date date) {
if(event == null) {
event = new EventImpl();
}
event.setWhen(date);
}
@Override
@Deprecated
/**
* {@inheritDoc}
*
* This function is actually a shortcut to
* {@link EventFacet#getTheEvent()}.{@link Event#getWhat()}
*/
@JsonIgnore
public String getEvent() {
if(event==null) {
return null;
}
return event.getWhat();
}
@Override
@Deprecated
/**
* {@inheritDoc}
*
* This function is actually a shortcut to
* {@link EventFacet#getTheEvent()}.{@link Event#setWhat(String what)}.
*
* It eventually instantiate {@link Event} if null.
*/
@JsonIgnore
public void setEvent(String event) {
if(event == null) {
this.event = new EventImpl();
}
this.event.setWhat(event);
}
@Override
/**
* {@inheritDoc}
*/
@JsonGetter(EventFacet.EVENT_PROPERTY)
public Event getTheEvent() {
return event;
}
@Override
/**
* {@inheritDoc}
*/
@JsonIgnore
public void setEvent(Event event) {
this.event = event;
}
@JsonSetter(EventFacet.EVENT_PROPERTY)
protected void setGenericEvent(Object event) {
if(event instanceof String) {
setEvent((String) event);
}
if(event instanceof LinkedHashMap<?, ?>) {
ObjectMapper objectMapper = ElementMapper.getObjectMapper();
Event theEvent = objectMapper.convertValue(event, Event.class);
setEvent(theEvent);
}
}
}
With this strategy, any Java client reading the event facet instantiated with the old
model will remap the 'date' and 'event' properties to an instance of 'Event' named 'event'.
We receive
.. code:: javascript
{
"type": "EventFacet",
"id": "d9f91175-8c33-4f68-8619-48e6feca4e47",
"date": "2024-01-08 12:52:59.907 +0100",
"event": "certified"
}
which will be serialized in turn as:
.. code:: javascript
{
"type": "EventFacet",
"id": "d9f91175-8c33-4f68-8619-48e6feca4e47",
"event": {
"type": "Event",
"what": "certified",
"when": "2024-01-08 11:52:59.907 +0000",
"who": null,
"where": null,
"why": null,
"how": null
}
}
The problem of this solution is that if the facet is updated to the new format in the server and
there are old clients they will not be able to deserialise the new version.
This