Many web frameworks have very advanced support for the so-called “CRUD” operations, that is, Create, Retrieve, Update, Delete, also known in SQL as Insert, Select, Update and Delete. Often, when developers see the CRUD features in these frameworks – like data grids that know how to perform CRUD operations, as in SmartClient – they react by saying: well, that’s great for CRUD stuff, but my application only has a few database tables and has a lot of non-CRUD operations. Most of these features don’t apply to me.
This perception is quite wrong, as we will come to see. The reality is:
- Many operations that are perceived as “not CRUD” are actually CRUD operations: People confuse CRUD with “business objects stored in a database”. CRUD is actually far broader than that – it basically encompasses any operation performed on something that can be modeled in the object-oriented style. We’ll see several examples of this.
- CRUD modeling almost always helps, and never hurts: CRUD-style modeling of seemingly non-CRUD operations is almost always beneficial. It can bring benefits in terms of automatic error handling, performance, monitoring, and in many other areas. It also makes your “non-CRUD” operations easier to understand because they are handled in the same way as normal CRUD operations.
- The Myth: My application has lots of non-CRUD operations
- Supposedly “non-CRUD” operations that are absolutely CRUD
- How to find the CRUD way of looking at a given operation
- Use CRUD even when it’s really not CRUD
- The benefits of applying CRUD
- About the Author
The Myth: My application has lots of non-CRUD operations
The idea of CRUD originates in ER (Entity-Relationship) modeling. The first thing to understand about ER is that ER models simply reflect the way humans think. ER models are a natural representation of data, not an artificial one invented to make coding easier.
Specifically, humans naturally group things into types of objects (cars, people, orders, buildings, etc.), and those objects have attributes (cars have color and number of doors, people have name and height, orders have ship date and a status, buildings have total number of floors, architectural style, and so on). The core of ER is just this: there are objects, and those objects have attributes, and can be related to each other.
Because ER really just reflects the way people think, ER modeling really applies to almost everything, not just to a narrow range of classic “business objects” like People, Accounts, and Orders.
Further, ER modeling has nothing to do with how data is stored or even whether it is stored permanently at all. ER modeling is just a way of thinking about and describing something. ER modeling is not limited to SQL, and can even be applied to completely ephemeral client-side concepts that never have server storage (for example, outstanding requests).
ER is also the conceptual underpinning of object-oriented programming. This means CRUD can also be applied to anything you might create as a class in an object-oriented programming language such as Java. “Create” is calling the constructor, “Update” is calling some setters, “Delete” is explicitly or implicitly discarding the object, and “Retrieve” is filtering a list.
With this clearer and broader understanding of what CRUD really means, let’s look at some examples.
Supposedly “non-CRUD” operations that are absolutely CRUD
In my experience, it’s very common for a developer to incorrectly assume that a given operation can’t fit in an ER model, and then go off and implement something custom, throwing away the massive benefits that a framework can offer around ER models – not just performance, but simplicity: error handling, uniform code structure, and other benefits.
Here are several recurring examples that will help to understand just how often “CRUD” works as a modeling strategy:
1. Background processes
You need to kick off a long-running process on the server. Well, initiating the process is an “add” of a new BackgroundProcess record. Checking on the process is a fetch of a “BackgroundProcess” record by its unique ID (and/or userId for multiple such processes), and the status of the process is simply a field on the BackgroundProcess record.
Is there an output of the process, like a URL or file? That’s just a field in the record that is non-null once the process is completed. Want to cancel the process? That’s just a “delete” of a BackgroundProcess record.
2. “Documents”, “files” or other blobs containing non-ER content
You may have an OOXML document, binary image, XML/JSON blob, Java-style .properties snippet, or whatever. The interior content – the OOXML document, for example – may not be modeled in an CRUD/SQL style.
You may be storing it in a “NoSQL”-style DB. None of that stops you from representing each such document, image, or file as an entity/object/row in an ER model.
There are key metadata fields – lastUpdate, ownerId, dateCreated, etc. – that fit perfectly into an ER model, and allow you to leverage caching, validation, and error reporting features that are built-in to the ER model. The “special” field – the one containing the blob of content that is not an ER model – is just another field of the record.
3. Asynchronous messages & reliable delivery
Sending a message is an “add” on the Messages table. Use a subsequent “fetch” to check the Message status, whether it was delivered, had an error, etc. Want to cancel a Message that may not have been delivered yet? That can be an “update” to set status to “Cancelled”, and if it’s too late to cancel message delivery, then that’s a validation error on the status field.
Logging in can be represented as just a “fetch” on the User table with hashed credentials. If login is successful, the active sessionId is returned as a field value. Then, retrieving session data is a “fetch” on the Sessions table with sessionId as criteria. Logout is a “delete” on the Sessions table.
5. Calculations, estimates, or quotes
Why not represent these as a “fetch” on a Calculations table, with inputs to the calculation expressed as criteria? Automatic caching can prevent duplicate calculations.
You can apply validators to inputs to the calculation, and you get client and server enforcement. Putting a quoteDate field on a Quote entity allows you to express validation rules, such as making sure that no save relies on a Quote that is too old. And so on.
6. Exceptions or error data
Perhaps something can go wrong in a long-running process or server workflow. Store these as “Errors” or “Exceptions” so you can look them up by the ID of the process, workflow, or whatever system may run into issues. Then you can later “delete” them or set status to resolved, etc.
How to find the CRUD way of looking at a given operation
Many developers have a kind of “reflex” to take any operation that isn’t a SQL operation on a business object and assume it needs to be represented as a custom, non-CRUD operation. The reality is that it’s rather rare to find an operation that cannot be understood as a SQL-like operation on an entity; almost everything fits into the CRUD model, and fits in cleanly (not as a hack!).
If you believe you have an operation that simply cannot fit into a CRUD model, first, simply try to describe what needs to happen as if speaking to a colleague. Doing this, you will often just trip over a natural CRUD representation: “I need to start a new background process.. oh.. that could be an add on a BackgroundProcess entity”.
If you miss the opportunity to represent an operation as a standard CRUD operation, you will miss out on automatic validation handling and/or intelligent caching that would be automatic if you used the standard CRUD approach, and worse, you will often end up building unnecessary, bespoke versions of capabilities that are built into the core CRUD model.
For example, multiple times, I have seen people build a form to kick off a background process, and once they realized there were error conditions to handle, they went and created a custom error reporting format and added custom code to the form to receive, process, and display the error.
This is all totally unnecessary – represent the operation as an “add” of a new BackgroundProcess, and all of the above is automatic. That includes client-side checks that can prevent an invalid request from ever needing to be caught by the server!
Use CRUD even when it’s really not CRUD
Let’s say you’ve found an operation that just really is not CRUD. There’s just no way to conceptualize this operation as an operation on an object, even with the above tools, which enabled us to think of something as abstract as a calculation as a CRUD entity.
This is a rare situation. And even in this situation, guess what: you still want to use CRUD.
Consider the core CRUD operations, forgetting for a moment that they are usually conceptualized as operations on sets of similar objects. Instead we’ll just look at the inputs and outputs in abstract form:
- Create: Takes a series of inputs with specified types, returns something (typically an ID or the entire new record)
- Retrieve: Takes a series of inputs with specified types (criteria), returns a list of similar objects
- Update: Takes an ID and a series of inputs with specified types, returns something (typically a status, or the updated record). Alternatively, no ID, just a series of inputs with specified types (for multi-record updates)
- Delete: Takes an ID, returns something (typically just a status, or the number of affected records)
These operations are so fundamental that they can used to model basically anything. In particular, “Update” and/or “Create” can model any method call.
So, forget about ER or object-oriented modeling for a moment, and just consider the fundamental inputs and outputs of the operation you’re implementing.
Does your operation take a couple of inputs that come from an end user typing something in, and can there be validation problems with those inputs? OK, use CRUD (“create”). Otherwise, you will reinvent validation handling.
Does your operation return an Array of Objects? Use CRUD “retrieve”. Then you don’t have to invent your own bespoke protocol for passing inputs and receiving outputs. Also, if you ever want to display things in a grid or drop-down, or ever want searching and sorting, it’s free – it comes as a free benefit of representing your operation as a CRUD operation of whatever type.
In short, if you ever have an error case that is relative to a specific input, it’s CRUD. If you ever get back a list, it’s CRUD.
Yes, CRUD really is that universal.
The benefits of applying CRUD
OK, you’ve been convinced that any operation can be modeled via CRUD. Let’s circle back and remember why that’s important: it’s because many frameworks offer a lot of really advanced features and optimizations that apply to CRUD.
If you make everything CRUD, you get to leverage all of those features. And, as we’ve discussed, CRUD modeling is a natural approach for virtually any operation – so you’re not using a hack if you model things as CRUD, in fact, in most cases, CRUD modeling adds clarity and uniformity, making your application easier to understand and easier to maintain.
As a final point, here are just some of the benefits you get when you use CRUD modeling in our technology, SmartClient:
- An instant form for providing inputs to the service, with editors that match the specified types of your inputs (for example, a drop-down for an enumerated input, or a date editor for a date input)
- An instant grid or detail view for showing the results from the server
- Per-input validation and standardized error handling, including, for example, displaying errors for end users
- Many, many built-in optimizations around caching of responses, or catching validation errors before the server is contacted
- Built-in tooling, like being able to interactively test your operation with an auto-generated form for inputs, and auto-generated display of outputs (field by field, or a data grid). Also, that auto-generated grid gives you search/sort/pivot/grouping, and can export to spreadsheets, JSON, XML or other formats.
- The ability to work with your operation in Low Code visual designers like Reify.
What’s not to like?
If you have any feedback on this article, I would love to hear from you! The best way to get in touch is to Contact Us.
About the Author
Charles Kendrick has been the head of Isomorphic Software for over 20 years. He is the Chief Architect of SmartClient and Reify. Both products are heavily used and very popular amongst the Fortune 500. He has successfully worked with and coached dozens of teams delivering high performance systems and is a pioneer in the field of web application optimization.