Encapsulating my Entities

  

I'm developing a prototype.  I have an eSpace that has an entity, Person, with several attributes and another eSpace with a webscreen that will use it.  One of the attributes is LastUpdated, which I want to automatically set to the current date/time any time the record is updated.  I also want to do it automatically so that no developer can 'forget' to update this field.  Here all all my ideas and what my issue was.

1) Set default value - only works the first time obviously.

2) Modify the CreatePerson action - can't be done since it is grayed out, I assume because it is a system generated action.

3) Created a structure to mirror the entity with appropriate entity attributes  - setting each field can be done but seems error prone if there are lots of fields and eventually, there will be.

4) Created a structure to pass a Person record, made the structure public and Person private - fails as it requires that Person also be public.

5) Create a new action, CreatePersonAction that sets the field and then calls CreatePerson - this doesn't prevent someone from calling CreatePerson, which requires it to be public.  If the generated CreatePerson action was shown in the Action tab and had its own public attribute, that would solve my problem but it doesn't provide that option.


While I have this issue setting this one field, I'm actually trying to accomplish all of the following.

1) Isolate the data layer so that only one or two developers are authorized to change it but give full access to everyone else to use it, update records, etc.

2) Prevent developers from calling the default generated actions on entities since they will never be used in my application except by my actions that encapsulate them.  This is particularly true for the Delete action.  For historical purposes, I cannot allow records to be deleted.  I will have an action that sets the Status entity attribute to Inactive instead of using the Delete action.

3) Ability to validate or set fields as needed in the encapsulating functions.


Based on what I've done it looks like my best option is number 3 but it's cumbersome and potentially error prone.  So, can anyone out there that can tell me where I'm going wrong?  Thanks in advance for your help.

Hi Curt.
You have a fifth and better option.

You need to set your entity to Public:Yes with Expose Read Only: Yes.
This way you won't expose the platform provided actions to the upper layers.

Then, in that same eSpace create public User Actions to wrap the operations you need, that can by themselves call the platform Create/Update/Delete.

This ensures the only way to write on that table is through your exposed wrapper, wich can implement any amount of logic you need.

For instance, let's say you want to wrap the CreateOrUpdate Action.
You would create a user action named Person_CreateOrUpdate, with a single input as a Record of your entity type, the Output is the record Id.
Inside it, you can always fill in the desired fields, sucessfully overwriting whatever the developer may or may not enter for those fields, finishing with the use of the system CreateOrUpdate and assignment of the output parameter.
After I wrote that I was thinking about that option but the way I read the help text it indicated to me that the data would be read only, not that the actions exposed would be read only.  I'll work on that tonight!!  Thanks for the quick response.
Works exactly like I wanted.  The documentation could be improved to make it more obvious that basically what the Espose Read Only setting does is make five of the six generated functions private.  It does not prevent data from being written if you provide other ways of updating.  

Thanks again!
Curt -

Yes, that's the right approach, it's what we use in all of our applications. We call ours "Upsert".

Another thing we do is to standardize the output:

* ID
* IsSuccess
* Messages

Messages is a Record list of the Message structure which contains "MessageText" and "MessageType" (which comes from RichWidgets). As error or success messages accrue in an action, we build them up into Messages.

Inside each of these, we have an "All Exceptions" handler which takes the exception message, properly rewrites it to somethign friendly, and puts it into the Messages with IsSuccess = false.

This means that you can call the action, send Messages.Current.MessageText and Messages.Current.MessageType to the FeedbackMessage Action to put success/error messages up, and check IsSuccess to determine screen flow.

The end result is that we never see unhandled exceptions to the screen anymore, and our users virtually never get cryptic error messages, and we've properly separated logic from screen.

Using this style of work, we've been able to do things like go from desktop to mobile in minutes or hours instead of days, and rapidly/accurately refactor as needed.

J.Ja
Fantastic stuff!!  Thanks!
Curt Raddatz wrote:
Works exactly like I wanted.  The documentation could be improved to make it more obvious that basically what the Espose Read Only setting does is make five of the six generated functions private.  It does not prevent data from being written if you provide other ways of updating.  

Thanks again!
Hi Curt!

Thank you for this feedback! We'll add this to our backlog and improve the reference documentation on that direction.

Cheers 
 
Justin James wrote:
Curt -

Yes, that's the right approach, it's what we use in all of our applications. We call ours "Upsert".

Another thing we do is to standardize the output:

* ID
* IsSuccess
* Messages

Messages is a Record list of the Message structure which contains "MessageText" and "MessageType" (which comes from RichWidgets). As error or success messages accrue in an action, we build them up into Messages.

Inside each of these, we have an "All Exceptions" handler which takes the exception message, properly rewrites it to somethign friendly, and puts it into the Messages with IsSuccess = false.

This means that you can call the action, send Messages.Current.MessageText and Messages.Current.MessageType to the FeedbackMessage Action to put success/error messages up, and check IsSuccess to determine screen flow.

The end result is that we never see unhandled exceptions to the screen anymore, and our users virtually never get cryptic error messages, and we've properly separated logic from screen.

Using this style of work, we've been able to do things like go from desktop to mobile in minutes or hours instead of days, and rapidly/accurately refactor as needed.

J.Ja
This is exactly what I am trying to do, and being new to OS am having a little difficulty with it, I would love to see an example of this if you have one. For an 'optimistic lock' update " ... where new.timestamp = existing.timestamp,  I don't seem to be able to detect when the Db actually updates the row vs. when it fails to find the row to update.

Thanks
 
Keith -

I don't have anything handy that I can post up here, but if you want, we can book a quick screen share on one of my evenings and I'd be glad to show it to you.

J.Ja
Justin James wrote:
Keith -

I don't have anything handy that I can post up here, but if you want, we can book a quick screen share on one of my evenings and I'd be glad to show it to you.

J.Ja
 Justin,

Thanks very much for the offer. 

But I'm very pleased to say I've actually figured it out for myself now so won't be disturbing you.

And thanks for all the other information you have and continue to supply to the forum.

Regards

Keith
 
Keith -

Glad you got it, and not a problem! Once you implement this pattern once... you'll find that you never want to go back to calling the bare CRUD calls again. :)

J.Ja