Create or Update CS Pattern

I’m considering that if you’re interested in this topic, you might be a OutSystems developer and each time you create a new database entity, you create a public action in the related CS module named «EntityName»_CreateOrUpdate.
I’ve seen multiple ways to define the code that it’s wrapped inside this action, but I also ended up by implementing my default patterns and this ideas are what I want to share with You.

As we could see in the previous articles:
1. CRUD Pattern – Alert nr. 1 – I’ve shown you a way to avoid changing fields that are supposed to be immutable;
2. Validation is Golden Rule – I’ve enforced an idea that you should validate action inputs before executing logic;
3. The importance of Trim() – I’ve shown you a way to avoid saving unwanted space characters into database;
4. Avoiding Database Update Concurrency – I’ve described a way to avoid concurrent data updates into database;

And now we’ll wrap everything we learned into a CreateOrUpdate action.
I’ll be using an incremental build process to be able to discuss pattern by pattern…

For the sake of this example I’ve created the Contact entity and the respective CreateOrUpdate action:

The basic structure that I see implemented everywhere is the following:

Explanation:

  • The Action receives a record from the respective entity;
  • If the id of the record is null, then we assume that this is a new record, otherwise it’s an existing record that we need to update;
  • If it’s a new record:
    • We automatically assign CreatedBy and CreatedOn audit fields;
    • We call the CreateContact and return the new Contact Id;
  • If it’s a existing record:
    • We automatically assign LastModifiedBy and LastModifiedOn audit fields;
    • We call the UpdateContact and return the ContactId from the existing record;

Pattern 1 – The code is not preventing a malicious user to change the CreatedBy and CreatedOn fields when updating a single record.

As you see there’s nothing that protects CreatedBy and CreatedOn fields, the caller to this action can change or delete that information and the code is not protecting it from happening.

How to fix?

Explanation:

  • Before Updating the existing Contact, we need to load the record from the database;
  • Then we re-assign CreatedBy and CreatedOn fields using the values stored in the database (if you have more immutable fields, you should recreate the same behavior for those fields);
  • This way, CreatedBy and CreatedOn fields will be immutable (as long as you use this action);

Pattern 2 – The code is not detecting if the ContactId exists on the database before executing the UpdateContact action

As you can see, we run UpdateContact by assuming that Contact.Id exists.

Let’s change that:

Explanation:

  • If GetExistingRecord returns empty, this means that the record with the provided Id does not exist on the database;
  • If record does not exist, we throw a new RecordNotFoundException with the detailed message for troubleshoot;

Sidenote regarding RecordNotFoundException – You need to decide what to do when this exception occurs. Suggestion:

  • Create an internal log to debug and troubleshoot why this happened. A possible explanation is that the record was deleted after the user loads it. By trying to save the record, the record is not found and the exception will be triggered;
  • Give feedback to the user when the feedback is valid. This can be confusing but if the action is being executed in the scope of editing the Contact record, you can provide a friendly message to the user such as “Contact not found”, however if this action is being called in by another orchestrator action, the same message might not make sense to the user. You should never consider that a single action is called in the scope that you created it. This might be true when you create the action but meanwhile the same action could be called by multiple other actions in different contexts;

Pattern 3 – Concurrent Updates

As you can see, nothing is protecting concurrent updates to the same record. In this scenario last save wins.

Let’s fix that:

Explanation:

  • By checking if the LastModifiedOn datetime is different between what’s being received by the action and the value stored in the database, we can detect if the record was modified meanwhile;
  • If the LastModifiedOn fields mismatch, then we throw a new ConcurrencyException with the detailed message for debugging;

Sidenote regarding ConcurrencyException – You need to decide what to do when this exception happens. Suggestion:

  • Consider that creating internal log is not required because concurrency conditions will be created and solved by the end user;
  • Give feedback to the user when the feedback is valid. This can be confusing but if the action is being executed in the scope of editing the Contact record, you can provide a friendly message to the user such as “Record was modified since you loaded it, please reload the page again”, however if this action is being called in by another orchestrator action, the same message might not make sense to the user.

Pattern 4 – Required fields validation

Consider that field Name is required. By defining it as Mandatory in database entity diagram, this does not enforce any database validation on the field itself. This means that if no validation is made in the top modules, we would be able to save Contacts without name.

Let’s fix that:

Explanation:

  • Do a field by field validation in the beginning of the action (take in consideration if the validation is valid for both or single Create or Update flow) and throw a ValidationException providing a friendly message.

Sidenote regarding ValidationException – You need to decide what to do when this exception happens. A possible solution:

  • Create an internal log to debug and troubleshoot why this happened. Previous code should prevent this validation to fail.
    Screens that create or edit new Contacts, should do client side and server side validations and provide the correct feedback to the user. This exceptions guarantee that
  • Business Logic and Frontend modules should pre-validate the same conditions when this action is being executed in the scope of creating/updating the Contact entity.

Pattern 5 – Empty Strings

In previous example we saw how to validate missing name, however if the user inputs a space character in the name field, the action would work because Name is not an empty string.


Let’s fix that:

Explanation:

  • Before any validation, we should apply Trim to all text fields. This will remove all leading and trailing space characters (‘ ‘) from string;
  • In our example, I’ve applied it to:
    • Name – This is a required field, so it cannot be empty.
    • EmailAddress – This is not a required field, but we cannot have duplicate email addresses (we’ll see this pattern latter). By allowing spaces we could potentially allowing the same email address to be duplicated by prefixing or suffixing it with space characters;

Pattern 6 – Preventing records with the same field value

In our scenario, each contact can have an associated email address, however, different contacts cannot have the same email address, it must be unique.

The first thought that can be in your mind is that we can create a new Unique Index in the database however:

  • Email field is not required, so only one Contact could exist in the database without Email field filled in (if we create the unique index);
  • We want to provide friendly feedback messages. Hitting the index constraint would result in a very technical message that cannot be displayed to the user;

Let’s change our code then:

Explanation:

  • EmailAddress, if filled in, must be unique, so this rule must be validated for create or update flows;
  • If the user filled in the email address, then we query database for existing contact with the same email address but not the same record;
  • If a record already exists, we throw a ValidationException with the specific message;

Pattern 7 – Locking Record for Update

You could argue that Pattern 3 was incomplete because a racing condition can occur between executing GetExistingRecord and UpdateContact.

Let’s update code to lock record for update:

Explanation:

  • By calling GetContactForUpdate, we guarantee that the record is locked in the context of the current thread and will only be unlocked when UpdateContact is executed or Transaction ends (successfully or via rollback);
  • As we want to provide friendly exception messages, we still need to keep GetExistingRecord aggregate to check if the record exists in the database, however by adding GetContactForUpdate we ended by duplicate access to the database to get the same record. This explains why i didn’t add this information to the Pattern 3 discussion and left it isolated;

As you can see we incrementally updated a simple Create or Update action to a more complex version. We only validate one single required field (Name) and a single field that must be unique in the database (EmailAddress), but we could have a lot more fields to do the same kind of validation, leading to a longer and complex version of the same action.


Final Thoughts:

  • Validations should take place in every single layer, not considering that previous layers should/have done the validation;
  • Duplicating validations is always better than not validate whatsoever;
  • CS Layer is the last line of defense when storing database data, so validations should be taken in place in order to retain data consistency and integrity;
  • When developing a single action, consider that you don’t know what the call stack (you don’t know which action was the caller), so you need to validate the information that’s being provided. You would not open your door house and let a strange cook you a meal just because he said i’s a chef.