/ Development  

Revolutionary hack; The surprising way to version your entity instance in real-time

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

Versioning an entity instance? Hmmm…Where is this thought coming from? Well, I’m also a Documentum consultant and with an Enterprise Content Management system you have this functionality out of the box (when you check-out/check-in content). An entity has of course content related building blocks (like ‘Content’ and ‘File’) that support such functionality (when connected with the appropriate content storage provider like Document or Content Server!). All great and interesting, but how about making a version or the entity instance itself!? Why would we want to do this? Well, I just want to see if it’s possible to low-code such functionality and what bumps in the road will we face during implementation…


Let get right into it…

Where to start with this version implementation? A plan would be nice, so let’s produce a list of features we would like for a version implementation on an entity instance:

  • A version (for me) is just a copy of the current entity with a new selectable major/minor version
    • My first question here would be: How about related entities and other BBs data?
  • We don’t need the implementation of a “Same version” feature like we know from content (which overrides the current version content!)
  • We create a new version via an action rule with an option to select what our next version (major/minor) will be
  • The list of entities as well as the entity itself will have an option to “Show all versions”
  • It should be possible to delete the last version so the version before that one will be the new current version
  • We can create a current version out of a previous version
  • We can delete including all previous versions

Building time; Spin up your VM, dive into your organization and workspace, and create your first entity with the name document. You can use this table of properties as input for the entity creation task:

Label Name Type Length
Name doc_name Text 64
Is current version doc_version_is_current Boolean
Major version number doc_version_major_nr Integer
Minor version number doc_version_minor_nr Integer
Version label doc_version_label Text 32
Version doc_version Text 64
Next version doc_next_version EnumText (static) 8
Next version label doc_next_version_label Text 16

Next on the list would be making it all nice and publishable:

  • Make default generated BBs bright and shiny with interesting new names (from your naming convention!)
  • I extend the ‘List’ BB lst_all_documents with the Identity.Id column to have a clear view on what’s happening during my copy actions (keep reading…)
  • Remove the ‘none’ option from the ‘Boolean’ property and set the default value to true
  • Add Major and Minor as selectable Enum values; Minor is default (remove the default ‘none’)!
  • Major starts with minimal 0; set default value to 0
  • Minor starts with minimal 0; set default value to 1
  • Add the ‘Web service’ building block and expose all CRUD operations
  • Update the lengths of all the ‘Text’ properties (check that table again…)

After these quick updates we create an ‘onInit’ rule BB setting the version text to 0.1,CURRENT. I name the rule e_oninit_set_doc_version and let it set the property doc_version with this expression: item.Properties.doc_version_major_nr + '.' + item.Properties.doc_version_minor_nr + ',CURRENT'

Now, do a first publication, and create that first document in runtime:

version_001

Ohw, I also updated the ‘Create’ form! 😁

With that first initialization step ready, we move to the next step!


Create a copy of an entity

Now, we’re getting into advanced logic as there is no default action available making this feature available in runtime!? So, now what? Well, when we’re talking advanced logic, we talk BPMs…Let’s create one! Real simple with 3 activities…Just like this:

version_002

Set the execution mode the ‘Short Lived’ as we would like to get direct feedback in runtime; Name it bpm_create_version and save it in the bpms folder of the project.

Back on the ‘Document’ entity, we need a modal screen/form which we’ll show just before we trigger our create-version BPM! This makes it possible to provide some settings for the next version; Like the Major/Minor selection, and an additional version label. The form (of type ‘Cancellable’) is named frm_version and will look like this:

version_003

With this new form in place, we can now create a second rule; Not an ‘event’, but this time of type ‘action’; I name it a_create_version with a proper label. This action will trigger our BPM managing the advanced logic, but before the action is performed we show that ‘Cancellable’ form! Simple and efficient like this:

version_004

Notes:

  • The condition is wisely chosen; So, we can only create a new version on the latest version.
  • After review of this post, the form name would be nicer with a name frm_create_version; We’ll create a new form frm_show_versions later on…

Publish all that challenging work; From runtime we can now trigger the action from a “current”, ‘Document’ entity instance:

version_005

Double-check the PIM artifact for that BPM instance!

Before we move to the BPM implementation, we first create a new service container of type ‘Application Server Connector’. You can use this input during the wizard from the ‘System Resource Manager’ artifact point of view:

  • Connector type: Application Server Connector
  • Group name: sg_appserver
    • Select the method set of our ‘Document’ entity
  • Service name: sc_appserver
    • Startup: automatically
    • Assign to OS (recommended by OT by default for all service containers!)

With this service container supporting our webservice CRUD operations of the ‘Document’ entity, we’re now ready to implement this pseudocode for our BPM:

1
2
3
4
5
6
7
8
9
10
11
12
13
var document = ReadDocument(ItemId);
var nextVersion = document.getNextVersion();
var nextVersionLabel = document.getNextVersionLabel();
switch(nextVersion) {
case 'major':
document.setMajorVersionNr(document.getMajorVersionNr + 1);
break;
case 'minor':
document.setMinorVersionNr(document.getMinorVersionNr + 1);
break;
}
var newDocument = CreateDocument(document.getAllData(), currentVersion = true);
UpdateDocument(currentVersion = false, nextVersionLabel);

Time to open the BPM document bpm_create_version; The one (short-lived) with those three activities in place. You can inject the first activity with the webservice ‘Readdocument’ (from the right-click-context-menu of the activity!). The second will have an injection of ‘Createdocument’, and finally we’ll inject ‘Updatedocument’ in the third activity. We keep it simple for now with a first messagemap implementation like this:

version_006

I see a mismatch in the screenshot (after reviewing this post!) for the ‘Createdocument’ service call. The target doc_version_label should have a match with the source doc_next_version_label…An extra task for you! 😁

Publish it, and try it out. A new instance is created in runtime, the old instance will be removed from the current state; You can easily filter it out from the filter option (which you can also save since version 21.4):

version_007

NICEEEE…Let’s enhance this with an (exclusive!) decision split…WAIT, let’s do something smart here; without that split; Have a look:

version_008

Don’t forget to update the usage option; These are the expressions behind it:

1
2
3
4
5
6
7
8
9
//MAJOR property
if(ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version/text() = 'major')
then ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_major_nr/text() + 1
else ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_major_nr/text()

//MINOR property
if(ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version/text() = 'minor')
then ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_minor_nr/text() + 1
else ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_minor_nr/text()

Publish, and try out…

version_009

Works smoothly! The only thing not working is the ‘Version label’ update itself; It’s still 0.1,CURRENT! For this we just add a new ‘onChange’, ‘Rule’ BB with the nice name e_onchange_set_doc_version doing a fancy ternary expression like this:

version_010

This is for you to copy from:

1
item.Properties.doc_version_is_current ? item.Properties.doc_version_major_nr + '.' + item.Properties.doc_version_minor_nr  + (item.Properties.doc_version_label is null ? '' : ',' + item.Properties.doc_version_label) + ',CURRENT' : item.Properties.doc_version_major_nr + '.' + item.Properties.doc_version_minor_nr

Note the nested ternary operator!! GREAT stuff, once you experience it! Watch the between-brackets-part to make it work! 😉

Now the great question…Is this smart doing it like this; Via an event rule while we’re already in BPM-land? My honest answer would be a “NO”! Does it work? For sure, but it separates two dependent logics into 2 levels of complexity! Keep things together if possible, so we’ll remove the rule again and implement this logic into the ‘Createdocument’ activity of the BPM with a new consolidated BPM view like this:

version_011

This is the expression setting the doc_version value:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
concat(
if(ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version/text() = 'major')
then ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_major_nr/text() + 1
else ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_major_nr/text()
, '.',
if(ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version/text() = 'minor')
then ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_minor_nr/text() + 1
else ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_version_minor_nr/text()
,
if(count(ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version_label[@nil="true"]) = 1)
then ''
else concat(',', ns2:ReaddocumentOutput/ns2:ReaddocumentResponse/ns3:document/ns3:doc_next_version_label/text())
, ',CURRENT'
)

It’s a bit hard to read, but it’s a concatenation of string values where the if-else statements make sure we optionally apply the correct labels we pass in from the modal version form selections! I learned this xPath language from the BPM-guru…You know who you are when reading this! 😉

Does it work? See for yourself (from a clean test):

version_012

If you watch closely it’s not working all that smoothly as I tell you! Two things that will have an improvement in the final downloadable sources at the end of this post:

  1. When you create a new major version, the minor version will start at 0…See that latest major version!
  2. The doc_version property is not correctly updated with the CURRENT tag…See those previous versions!

Great…what else?


Other features

All the basics for creating a new version are in place, let’s now have a look on the additional features:

🔥 The list of entities as well as the entity itself will have an option to “Show all versions”

Our default ‘All’ list already has the ability to filter out the current versions of the ‘Document’ entity instances. Let’s make an action rule available on a selected current version showing the other versions in a modal popup! This will behave the same as the previous action rule to create a new version. So, let’s make a copy from a_create_verions and give it a new name a_show_versions and low code it like this:

version_013

The frm_show_versions needs special attention! Why? Well, we would like to show a toMany-relation-grid to itself!? 😅 Is this possible? Sure…Just create a new relationship BB with name to_many_document and place this relation on that new ‘Autosave’ form!? ‘Autosave’?? Yes, ‘Autosave’! Why not ‘Cancellable’? Because it’s not possible to add a ‘toMany’ relation on a ‘Cancellable’ form! Yeah…Please, don’t shoot the messenger!

version_014

I also unchecked all the available actions (falling outside the screenshot) like ‘Open’, ‘Create’, ‘Browse’, etc. for this grid!

With this relation created and the form updated, we now only need to make sure the relations are put in place when our new version is created! This will be a BPM task which retrieves the previous versions and relates then to the new current version…Great, we have a solution! Now for the implementation…

First we move to the webservice BB where we enable the extra operations:

version_015

We also add that extra service operation to get a list of ‘Document’ entity instances by their ‘doc_name’; This is what’s behind the low-code service operation:

version_016

Notes:

  • We use the ‘doc_name’ as constant to get a grip over “seamless” versions, but a better solution would be to have a revision ID in place for this. A task for you!
  • We filter already the non-current versions here which makes it easier in the BPM!
  • In the ‘Results’ tab you can also add a “Sorting by”; If you sort descending by ‘Identity.Id’, you can add the last non-current version at the top!

Next is the BPM…I add 2 extra activities; The first one injects the ‘GetByName’ service; The other one will be a for-loop injecting the ‘AddToto_many_document’ service call:

version_017

I see there is a wrong defined arrow-flow in the above screenshot! Can you detect it? Yes, it’s the one directly connected as input to ‘AddToto_many_document’…You can remove it yourself! It’s a common mistake…That’s why I left it in the screenshot! 😇

‘GetByName’ requires the ‘doc_name’ as input from a messagemap point of view. Like this:

version_018

‘AddToto_many_document’ requires 2 mappings:

  1. A mapping between the new created document ItemId to the webservice ‘document-id.ItemId’
  2. A mapping between the current document for the loop to the webservice ‘to_many_document.document-id.ItemId’

This is the consolidated view for the two extra activities:

version_019

Time for a publication and a test in runtime! Is it working? Yesssss…It is!

version_020

The only “too bad” thing here is that we have a ‘Close’ button instead of a ‘Cancel’ button…But YOLO, who cares? Who will notice? 🙈

Next!

🔥 It should be possible to delete the last version so the version before that one will be the new current version

Now we’re talking business logic! Sounds like an extended ‘Delete version’ action (as a ‘Rule’ BB) to me. The only thing to do here is finding out if there is a previous version; If so, we need to figure out which (of all previous versions) is the last one before the current one. A simple task if we had set the previous version in a “helper” property, but as we have not designed it like this we just go by the latest ordering approach on ItemId; This is not the safest way to do it correctly, but it works. We could also add the ‘Tracking’ BB to the entity which exposed the ‘creation_date’ of the entity which make it more solid…It’s all up to you; I go for the order by ItemId approach for our post.

So, let’s create a new short-lived BPM with name bpm_delete_current_version. The best way to do this is by opening the previous BPM bpm_create_version and save it as new BPM. This way it’s already short-lived, and it also copies the runtime security (if applied!). You can remove all activities (except ‘Readdocument’ and ‘GetByName’) between the start/end construct for now and save it. With this BPM in place, we can now create a new action ‘Rule’ BB for our entity; The same exercise as before, but an implementation like this (I name that new rule a_delete_current_version):

version_021

After publication, we have an extra action available on the current selected versions for our ‘Document’ entity instances!

version_022

Next is that BPM implementation…It’s not that hard! This is my BPM with an exclusive check on the number of versions available:

version_023

This is the corresponding messagemap consolidated view to get a better understanding:

version_024

Publish and test…Is it working!? Hell yeah, it’s working! 🤠

I was thinking on how to remove the default ‘Delete’ action from the UI! First I thought of having an ‘Actionbar’ BB where I leave the ‘Delete’ action from possible options. Having the ‘Actionbar’ selected for my default layout does indeed the trick. Only, the ‘List’ BB still shows the action. The ‘List’ BB does not have a low-code feature to tell that it must use a specific actionbar…#FeatureRequest?? The only way removing the ‘Delete’ action would be the ‘Security’ BB and scope it from the list via a role. An easy task I leave with you as it’s out of scope for my post!

🔥 We can delete; including all previous versions

We can do this off-course already (selecting all entity version entities and hitting the ‘Delete’ action), but we also want to make this possible by selecting only the current version! Simple and effective with some BPM power (a ‘save as’ on the other BPM and naming it bpm_delete_all_versions) and a new action ‘Rule’ with name a_delete_all_versions triggering the BPM. I configure the action Rule with an extra “Confirmation” cancellable form which just shows the text “Are you sure?”:

version_025

This is my BPM:

version_026

With the consolidated messagemap view to have yourself a great implementation:

version_027

We could also update the bpm_delete_current_version with this functionality (with an interesting switch flag), but it’s always an excellent choice to make sure your BPMs do only one thing and that they also do it very, very well!…Just like in programming. See Single Responsibility Principle; Is the above BPM the only way forward? After a peer review, you can make it better; It’s my minimal viable product ready for improvements! 😁

🔥 We can create a current version out of a previous version

Owh yeah…More advanced stuff to build! I’m not writing it all out…You can have a look yourself in the sources downloadable here! After assessing my own low-code stuff, I implemented some fixes to make it all a bit smoother…That’s agile!


Answer to my own question

Question? Yes, this one: How about related entities and other BBs data? So, when our ‘Document’ entity has related entities, has a ‘Content’ or ‘File’ BB in place, has a lifecycle applied, has an assignee, deadline, discussion, or tracking BB data; Did I already mention the history, the new ‘Notification’ BB, or what about an applied and synchronized business workspace BB!?

Now we’re talking 2.0 stuff to further build a nice implementation around our versioning feature! Just some thoughts per building block…I leave the craftsmanship to you. 🤔

  • Relationship: This one depends on how you look at it! Should it be a copy so the old versions are still related to them, or should those relations be moved to the current version!? Ask you analyst guy/gal! Both implementations require advanced business logic via updates in our BPMs!
  • Assignee: In my opinion the assignee person/role must be retained on the current version. So, that’s an un-assign and re-assign via the assignee webservices (‘Create|Delete|Update Assignment’) in our BPMs
  • Deadline: We can discuss this one, but I would try to push your analyst guy/gal to have a new deadline for the new version! It is eventually a new entity instance with its own deadline conditions which can be different.
  • Lifecycle: Well, we create a new version instance meaning we also start a new lifecycle with it! We can move the lifecycle to a specific state based on the previous version, but it all depends…as always! By the way, this is the same for activity flows and dynamic workflows.
  • Tracking: A new entity instance will have its own tracking with modify-date, creation-date, creator-, and modifier-name. Leave it as is, would be my first thought.
  • Content/File: A new version can also have new content-blob…right? Now we’re getting close at the CMS check-out/check-in principles where versioning is already possible! I would copy the content to the new version, so you have it directly available, but requires a BPM deep dive again with content related webservices!
  • Business workspace: A new entity instance will have its own business workspace (via xECM) applied; I don’t see how we can/want to manipulate this from a versioning principle…comment me on your thoughts!?
  • Discussions: Well, a new version with a new discussion! You can always have a look at the old discussion in the previous versions.
  • History: Same here…New version, new history! Old versions will have their own history in place!
  • Notification: This is an interesting one! I think you still want to have the notification for the old entity version in place. You can’t decide if users applied for notification want to keep their notification settings in place for that new version…correct? Maybe work with a modal popup screen where you make it optional!? 🤔

Let’s finalize the post with a conclusion as we can discuss for ages about implementation choices and directions…


Nice; Nailed it…DONE! An interesting low-code implementation on versioning an entity instance in runtime. Again, we see also the power of BPMs when it comes to advanced logic! Not all things are solvable via a simple ‘Rule’ BB. Give it a go, try the implementation yourself and share any thoughts on improvements from your experience. Have a great weekend and CU in the next post; next week!

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?