AsyncTicket
(Automatically adding template at the end of the page.)
No edit summary
 
(7 intermediate revisions by 5 users not shown)
Line 1: Line 1:
<message>Write the content here to display this box</message>
=== Background ===
=== Background ===
AsyncTicket is a pattern you can easily add to your model, backed by recent MDrivenServers.
AsyncTicket is a pattern you can easily add to your model, backed by recent MDrivenServers.


Read about its background here: https://blog.mdriven.net/doing-stuff-later/
Read about its background here: [https://blog.mdriven.net/doing-stuff-later/ Doing Stuff Later]
 
'''Note:''' This feature is only available for the MDriven Server.


=== The SysAsyncTicket ===
=== The SysAsyncTicket ===
The solution to how to easily spin any kind of work and do it later – or async –  is implemented by part model-pattern, part framework support, and part MDrivenServer support. That being said, it is important that you have versions from 2020-Feb-28 - or later - for this to work as described below.
The solution to how to easily spin any kind of work and do it later – or async –  is implemented by part model-pattern, part framework support, and part MDrivenServer support. That being said, it is important that you have versions from 2020-Feb-28 - or later - for this to work as described below.
[[File:2020-04-14 11h42 40.png|none|thumb|433x433px]]
[[File:2024-12-07 21h32 04.png|none|thumb|433x433px]]
The contract that instructs the system - that you want this ability - is fulfilled by adding a class that is defined like this:
 
* Named '''SysAsyncTicket'''
== SysAsyncTicket ==
* Has attribute DeleteTime with DateTime-nullable type. This will be set by the server after your work has been executed to Now+KeepReceiptMinutes.
 
* Has attribute Done with DateTime-nullable type. This will be set by the server when work is done.
=== Attributes ===
* Has attribute Error with string (nullable optional) type. If there is an initial issue with the ticket – like if the RootId is not valid, or the ViewModel is nonexistent - you will see this property filled with information.
<code><span class="col-black">'''DeleteTime'''</span></code>: DateTime?
* Has attribute ExecuteEarliest with DateTime-nullable type. If you want a delayed start you can set this – but leave it to null for the server to execute it as soon as possible.
 
* Has attribute KeepReceiptMinutes with Integer type – with an initial value set to your liking. Signals that the auto cleaning of a used ticket should happen X minutes after the job finishes.
This will be set by the server after your work has been executed to Now+KeepReceiptMinutes.
* Has attribute RootId with string (nullable optional) type. This is resolved and set by the framework to the external id that the RootObject association points to. If it points to a yet unsaved object, the framework will discover this and set this property after a valid key is retrieved. If this extra pass was needed, the framework will automatically resave the SysAsyncTicket with the updated RootId property.
 
* Has attribute ViewModel with string (nullable optional) type. This must be a valid name of a ViewModel existing in your model – it does not need to be a serverside-ViewModel. Any [https://wiki.mdriven.net/index.php/Explaining_%E2%80%9CThe_ViewModel_does_not_require_a_root_object%E2%80%9D_warning rooted viewmodel] will make do. You should use the constants found in the class, ie: <code>ticket.ViewModel:=YourClass.ViewModels.SomeViewModelName</code>
<code><span class="col-black">'''Done'''</span></code>: DateTime?
* Has a transient(Persistent=false) single association RootObject navigable in the direction of the most abstract class you ever want to use for async work (ie the SysSuperClass in a standard model). It is important that this link is set to Persistent=false since pointing out a hyper-abstract-class in a persistent association is really bad practice. It would require the framework to ask all possible subclasses if they have the key (this process is called exactification of a foreign key). In a system with hundreds of classes, this means hundreds of queries – i.e. do not set persistent links to abstract classes if the key is stored outside the abstract class – it will work but it will hurt performance.
 
This will be set by the server when work is done.
 
<code><span class="col-black">'''Error'''</span></code>: String?
 
If there is an initial issue with the ticket – like if the RootId is not valid, or the ViewModel is nonexistent - you will see this property filled with information.
 
<code><span class="col-black">'''ExecuteEarliest'''</span></code>: DateTime?
 
If you want a delayed start you can set this – but leave it to null for the server to execute it as soon as possible.
 
<code><span class="col-black">'''KeepReceiptMinutes'''</span></code>: Integer=10
 
With an initial value set to your liking. Signals that the auto cleaning of a used ticket should happen X minutes after the job finishes.
 
<code><span class="col-black">'''RootId'''</span></code>: String? 
 
This is resolved and set by the framework to the external id that the RootObject association points to. If it points to a yet unsaved object, the framework will discover this and set this property after a valid key is retrieved. If this extra pass was needed, the framework will automatically resave the SysAsyncTicket with the updated RootId property.
 
<code><span class="col-black">'''ViewModel'''</span></code> : String?
 
This must be a valid name of a ViewModel existing in your model – it does not need to be a serverside-ViewModel. Any [[Documentation:Explaining “The ViewModel does not require a root object” warning|rooted viewmodel]] will make do. You should use the constants found in the class, ie:  
ticket.ViewModel:=YourClass.ViewModels.SomeViewModelName
<code><span class="col-black">'''RootObject'''</span></code>: Transient SysSuperClass
 
Has a transient(Persistent=false) single association RootObject navigable in the direction of the most abstract class you ever want to use for async work (ie the SysSuperClass in a standard model). It is important that this link is set to Persistent=false since pointing out a hyper-abstract-class in a persistent association is really bad practice. It would require the framework to ask all possible subclasses if they have the key (this process is called exactification of a foreign key). In a system with hundreds of classes, this means hundreds of queries – i.e. do not set persistent links to abstract classes if the key is stored outside the abstract class – it will work but it will hurt performance.
 
Once you have this in your model and the server sees it, the server will create 2 new administrative serverside jobs. One job will be looking for SysAsyncTickets that have null in the Done attribute. This job has a high frequency – just like a key assigning job would have – but it is more ok for the SysAsyncTicket-job since there is only one of it. The other job deletes tickets that have a DeleteTime older than now – this job has a very low frequency.
Once you have this in your model and the server sees it, the server will create 2 new administrative serverside jobs. One job will be looking for SysAsyncTickets that have null in the Done attribute. This job has a high frequency – just like a key assigning job would have – but it is more ok for the SysAsyncTicket-job since there is only one of it. The other job deletes tickets that have a DeleteTime older than now – this job has a very low frequency.


Line 30: Line 59:
  SomeSingleton.oclSingleton.LatestUsedCustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber+1;
  SomeSingleton.oclSingleton.LatestUsedCustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber+1;
  self.CustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber
  self.CustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber
Consider having the precondition of the AssignNumber method set to: <code>self.CustomerNumber->isNull</code>
Consider having the precondition of the AssignNumber method set to: <code><span class="col-black">'''self.CustomerNumber->isNull'''</span></code>


And the ticket:
And the ticket:
Line 45: Line 74:
Add the Priority Property to the SysAsyncTicket class:
Add the Priority Property to the SysAsyncTicket class:
  Priority:integer?
  Priority:integer?
If found, the OCL-PS query will include <code>->orderby(at|at.Priority)</code> and this will push jobs you know are short and should be done asap to the top of any queue that may form. 1 has a higher priority than 10 and 1 will be taken from the queue before 10 if both are present at the time of decision.
If found, the OCL-PS query will include <code><span class="col-black">'''->orderby(at|at.Priority)'''</span></code> and this will push jobs you know are short and should be done asap to the top of any queue that may form. 1 has a higher priority than 10 and 1 will be taken from the queue before 10 if both are present at the time of decision.


=== Download a Ready Package to Merge Into Your Model ===
=== Download a Ready Package to Merge Into Your Model ===
[[SysAsync package]]
[[Documentation:SysAsync package|SysAsync package]]


=== Gotchas ===
=== Gotchas ===
Line 59: Line 88:
  )
  )
The actual type of self is not a "Thing" but rather a "SubClassOfThing" - then you run into trouble with Synchronization. To avoid trouble, name the reference on what to execute like this:
The actual type of self is not a "Thing" but rather a "SubClassOfThing" - then you run into trouble with Synchronization. To avoid trouble, name the reference on what to execute like this:
    ticket.ViewModel:='SubClassOfThing.AssignNumber'
ticket.ViewModel:='SubClassOfThing.AssignNumber'
The problem is that the MDrivenServer would, in the first case, treat your object as a Thing (formally correct) and the changes recorded for Synchronization will assume it is an object of class "Thing" that has changed. When your client refreshes the server's data, they will be notified that a "Thing" with identity X has changed and your client will not be able to associate that to your object of class "SubClassOfThing".
The problem is that the MDrivenServer would, in the first case, treat your object as a Thing (formally correct) and the changes recorded for Synchronization will assume it is an object of class "Thing" that has changed. When your client refreshes the server's data, they will be notified that a "Thing" with identity X has changed and your client will not be able to associate that to your object of class "SubClassOfThing".


The generic way to name the reference to execute is:
The generic way to name the reference to execute is:
    ticket.ViewModel:=self.oclType.asstring+'.AssignNumber'
ticket.ViewModel:=self.oclType.asstring+'.AssignNumber'
     [[Category:MDriven Server]]
     [[Category:MDriven Server]]
{{Edited|July|12|2024}}
{{Edited|July|12|2025}}
 
[[Category:TOC]]

Latest revision as of 05:33, 24 February 2025

This page was created by Hans.karlsen@mdriven.net on 2020-04-14. Last edited by Stephanie@mdriven.net on 2025-02-24.

Background

AsyncTicket is a pattern you can easily add to your model, backed by recent MDrivenServers.

Read about its background here: Doing Stuff Later

Note: This feature is only available for the MDriven Server.

The SysAsyncTicket

The solution to how to easily spin any kind of work and do it later – or async –  is implemented by part model-pattern, part framework support, and part MDrivenServer support. That being said, it is important that you have versions from 2020-Feb-28 - or later - for this to work as described below.

2024-12-07 21h32 04.png

SysAsyncTicket

Attributes

DeleteTime: DateTime?

This will be set by the server after your work has been executed to Now+KeepReceiptMinutes.

Done: DateTime?

This will be set by the server when work is done.

Error: String?

If there is an initial issue with the ticket – like if the RootId is not valid, or the ViewModel is nonexistent - you will see this property filled with information.

ExecuteEarliest: DateTime?

If you want a delayed start you can set this – but leave it to null for the server to execute it as soon as possible.

KeepReceiptMinutes: Integer=10

With an initial value set to your liking. Signals that the auto cleaning of a used ticket should happen X minutes after the job finishes.

RootId: String? 

This is resolved and set by the framework to the external id that the RootObject association points to. If it points to a yet unsaved object, the framework will discover this and set this property after a valid key is retrieved. If this extra pass was needed, the framework will automatically resave the SysAsyncTicket with the updated RootId property.

ViewModel : String?

This must be a valid name of a ViewModel existing in your model – it does not need to be a serverside-ViewModel. Any rooted viewmodel will make do. You should use the constants found in the class, ie:

ticket.ViewModel:=YourClass.ViewModels.SomeViewModelName

RootObject: Transient SysSuperClass

Has a transient(Persistent=false) single association RootObject navigable in the direction of the most abstract class you ever want to use for async work (ie the SysSuperClass in a standard model). It is important that this link is set to Persistent=false since pointing out a hyper-abstract-class in a persistent association is really bad practice. It would require the framework to ask all possible subclasses if they have the key (this process is called exactification of a foreign key). In a system with hundreds of classes, this means hundreds of queries – i.e. do not set persistent links to abstract classes if the key is stored outside the abstract class – it will work but it will hurt performance.

Once you have this in your model and the server sees it, the server will create 2 new administrative serverside jobs. One job will be looking for SysAsyncTickets that have null in the Done attribute. This job has a high frequency – just like a key assigning job would have – but it is more ok for the SysAsyncTicket-job since there is only one of it. The other job deletes tickets that have a DeleteTime older than now – this job has a very low frequency.

The high-frequency SysAsyncTicket job will look up the root object from RootId – look up the referred ViewModel – and then execute the ViewModel just as a normal serverside job. Typically, this means it will execute the actions on the root-level in order from top to bottom then save the changed state that came out of those actions.

Updates 2020-03-03: No Need for a ServerSide ViewModel - Just Use a Method

Wouldn't it be neat if we could skip the ViewModel step for really small async cases like the number assignment above?

Yes! You can now send the Class.Method in the ViewModel property. The MDrivenServer will execute the method for the root object. However, it will first check the PreCondition of the method.

Given this, the sample above would skip the ViewModel+action and instead add a method on the Customer class:

Customer.AssignNumber:
SomeSingleton.oclSingleton.LatestUsedCustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber+1;
self.CustomerNumber:=SomeSingleton.oclSingleton.LatestUsedCustomerNumber

Consider having the precondition of the AssignNumber method set to: self.CustomerNumber->isNull

And the ticket:

let ticket = SysAsyncTicket.Create in (
  ticket.RootObject:=self;
  ticket.ViewModel:=’Customer.AssignNumber’ -- This will call the AssignNumber method on self of type Customer, in the server context
)

Note: Invoking a method differs from invoking a ServerSide-ViewModel as below.

let ticket = SysAsyncTicket.Create in (
  ticket.RootObject:=self;
  ticket.ViewModel:=Customer.ViewModels.SomeViewModel -- This will invoke the SomeViewModel-ServerSideViewModel with self of type Customer as root
)  

Updates 2020-07-01

Add the Priority Property to the SysAsyncTicket class:

Priority:integer?

If found, the OCL-PS query will include ->orderby(at|at.Priority) and this will push jobs you know are short and should be done asap to the top of any queue that may form. 1 has a higher priority than 10 and 1 will be taken from the queue before 10 if both are present at the time of decision.

Download a Ready Package to Merge Into Your Model

SysAsync package

Gotchas

Executing a Method on a Subclass

If you produce a SysAsyncTicket to execute a method like this:

  let ticket=SysAsyncTicket.Create in (
    ticket.RootObject:=self;
    ticket.ViewModel:='Thing.AssignNumber'
)

The actual type of self is not a "Thing" but rather a "SubClassOfThing" - then you run into trouble with Synchronization. To avoid trouble, name the reference on what to execute like this:

ticket.ViewModel:='SubClassOfThing.AssignNumber'

The problem is that the MDrivenServer would, in the first case, treat your object as a Thing (formally correct) and the changes recorded for Synchronization will assume it is an object of class "Thing" that has changed. When your client refreshes the server's data, they will be notified that a "Thing" with identity X has changed and your client will not be able to associate that to your object of class "SubClassOfThing".

The generic way to name the reference to execute is:

ticket.ViewModel:=self.oclType.asstring+'.AssignNumber'