Building CRUD wrappers is universally recognized as one of the most important architectural patterns to follow to build great OutSystems applications, yet there is very little information on why, or how, to build them. This session will walk through a CRUD wrapper framework, explaining how to build one from the ground up, laying the foundation for an excellent application.
2. | Building CRUD Wrappers| Building CRUD Wrappers
Justin
James
Chief Architect | Cloud Development Resources
@
in
justin.james@clouddevelopmentresources.com
/jmjames
3. | Building CRUD Wrappers| Building CRUD Wrappers
Before we start...
4. | Building CRUD Wrappers
There Are No Few “Architecture Laws”
● I do not know it all
● There will be times to not follow these guidelines
● This is constantly changing, evolving, and shifting as I learn new things
and experience new projects and address pain points
● HOWEVER… this is based on 10 years of OutSystems experience and
has covered virtually every project I have done
● If unsure… ASK!
5. | Building CRUD Wrappers| Building CRUD Wrappers
OK, let’s jump in!
6. | Building CRUD Wrappers
CRUD Wrappers?
● CRUD Wrappers replace the
“CreateX”, “CreateOrDeleteX”,
“UpdateX”, “DeleteX” Actions in
your projects
● They enforce business logic
● Central place for messages
and validation
● Reside in the Core Layer of
4LC
7. | Building CRUD Wrappers
OK, but WHY?
● Separation of concerns (4LC)
● Makes localization much
easier to have so much of the
important text in one location
● Easy to have auditing and
security
● Can easily build APIs by
putting Web Services in front
of them
8. | Building CRUD Wrappers| Building CRUD Wrappers
Basic setup...
11. | Building CRUD Wrappers
Standard Actions - Session_GetNormalizedSessionUserId():
● Returns the current User Id if it is not null
● Returns the Configuration_GetAnonymousUserId otherwise
● This allows us to always have a valid User Id for places where we
have a mandatory field for “who did this?” (CreatedOn, auditing,
logging, etc.) even when running as a Process, Timer, etc.
12. | Building CRUD Wrappers
Standard Actions - EntityActionResult_CombineMessages:
● Inputs a list of Messages
● Outputs the most severe MessageTypeId and the MessageText
values nicely put into one string
13. | Building CRUD Wrappers
Standard Actions - EntityActionResult_GenerateFromSuccess/Failure:
● Accepts a single text input
● Returns an EntityActionResult
● Sets IsSuccess
● Creates a single Message, adds it to Messages, sets the
CombinedMessageTypeId and CombinedMessageText
● Most frequently used way of getting an EntityActionResult
14. | Building CRUD Wrappers| Building CRUD Wrappers
Yikes… we’re here… no
more dilly dallying...
15. | Building CRUD Wrappers
X_GetCanRemove()
● Verifies if a record may be removed
● Function = YES
● Input parameter: Id, of type X Identifier
● Checks if the record exists and is not soft deleted already
● Checks if all child records can be removed, if removing the parent
would cause the children to be removed
● We should almost always keep the delete constraints to “Protect”
● Returns an EntityActionResult
16. | Building CRUD Wrappers
X_Remove()
● Function = YES
● Input parameter: Id, of type X Identifier
● Always starts with a call to X_GetCanRemove, throws a
ValidationException
● First removes any children (if that is the logic… it usually is not)
● For a hard delete, just calls DeleteX
17. | Building CRUD Wrappers
X_Remove() (Continued)
● For a soft delete:
■ Call GetXForUpdate
■ Set IsActive, UpdatedOn, UpdatedByUserId
■ Calls UpdateX
● Generate the EntityActionResult with a standard message that
includes the record’s name if possible (“Invoice “”” +
GetInvoiceForUpdate.Record.Name + “”” has been removed.”)
● Exception Handler: EntityActionResult_GenerateFromFailure
18. | Building CRUD Wrappers
X_Upsert()
● Function = NO
● Input parameter: Source, of type X
● Output parameters: Id (of type X Identifier), EntityActionResult
● Performs any validation, throws a ValidationException if it fails
● Sets any default values
● Branches on Source.Id = NullIdentifier or Source.CreatedOn and
Source.CreatedByUserId = null (Source is an extension Entity)
19. | Building CRUD Wrappers
X_Upsert() (Create Branch)
● Sets CreatedOn = CurrDateTime()
● Sets CreatedByUserId = Session_GetNormalizedUserId()
● Sets UpdatedOn = Source.Created on (guarantees they are the
same)
● Sets UpdatedByUserId = Source.CreatedByUserId (faster)
● Sets IsActive = True (no one should ever use this to create a deleted
record, prevents screens making deleted records)
● Calls CreateX
● Sets Id = CreateX.Id
21. | Building CRUD Wrappers
X_Upsert() (Final Pieces)
● Branches merge again
● Perform any “clean up” logic, like updating child records or related
records
● Generate the EntityActionResult with a standard message that
includes the record’s name if possible (“Invoice “”” + Source.Name +
“”” has been saved.”)
● Exception Handler: EntityActionResult_GenerateFromFailure
22. | Building CRUD Wrappers| Building CRUD Wrappers
Whew! We made it! Time
to use them!
23. | Building CRUD Wrappers
All ya gotta do:
● Make sure that you check the
.IsSuccess of Remove and
Upsert
● Use the
CombinedMessageText/Type
to message
● Wrap Delete/Remove buttons
with X_GetCanRemove()
● Find/Replace perfectly
updates