Love - Lo Code / No Limit
"The demos speak like silence... too much ideals and violence...."
The title and quote are a spin on an on old Bob Dylan song, and the picture is an idealization of the smallest level of information that one might think exists. What's been eating me up as of late is an obsessive pursuit of the systematic representation of complex business processes in a way that is elegant and correct. See, there has been a proliferation of frameworks, low code, and no code out there, and for various businesses. To understand this, you may know, I've been building one, this Corona that I have written about, for a particular niche. But there are others, with loads of options, and I'll come back with some reviews on the various ones increasingly available. However, it will turn out that retrofitting existing systems, or adopting elements of this "low code/no code" approach really depends more on how the data is arranged and designed than on any particular kind of editor used to represent this arrangement.
We can have code, low code, or no code, but all require somebody to put that data about a business hierarchy into a computer somewhere, somehow.
I'm going to walk you through how to build such an animal with me, a framework of sorts that is as code, low code, or no code as you want. Once we get through it, you can see that before you scream "Eeek, I have to throw out millions of dollars of capital IT spend and pay permanent rent to a low code vendor", know that you have more options. You retrofit existing systems to have these low code / no code space traits and in several ways. One way might be to build a service that handles it, with some shared libraries between existing systems or, you can create a service that can be used in such a way. These things are not actually rocket science.
Build your own Code / Low Code / No Code Framework
Let's start with the basics. It turns out that any process can be easily represented with a special relational scheme of objects. We all know this, intuitively, as we've been doing this for years. But we're going to lock things down with some definitions, by saying our system has two pieces, an object store and model, with some imposed consistencies that will prove useful.
An object store stores objects in a special way. An object is a record, with any reasonable number of fields on it. You can build out the object store to have nested objects but this actually proves to be both unnecessary and inflexible, and once we get into the model, you'll see why.
A Dictionary
To come up with fields and objects, you have to build a dictionary. The dictionary is there to ensure that a field on one object is semantically the same as the same field on another object. So, in the dictionary you put fields, for example, say, "Purchase Quantity", with a short internal name for binding, like "purchase_quantity", then the data type and format and some validations for the field itself. It's pretty handy to put in a moniker for your favorite GUI control to represent that field - if it can be represented. Also, multiple fields will wind up using the same kind of control. There may be many types of string fields, like name, street, and so on, but ultimately many of them will just be text boxes.
Next up, you have to build out classes. Classes are just uniquely named list of fields. So, a class internally might be represented as "name, description, field 42, field 33", and so on. Like the fields, the class itself will have its own number. Since you've got that number, you can then add a base class id. That part is absolutely critical.
Collections
Now we want to store our objects. If you are doing your own storage, like building your own database directly like I did, you can go ahead and get the byte size of the class and allocate space in memory or on a file for it somewhere. In a relational store, when we create an instance of the object, we would check the class definition, create a table to match, if necessary, and then put the row in the that table. What's important to understand is that the objects go into a collection, and that, a system can have more than one collection.
Simple so far. Now, how do we do anything with these objects. Regardless of how it is implemented, let's formally define what we want a collection to do, as an absolute minimum. Putting an object into the store gives it a unique id, but getting them out, requires a few special tricks that we often don't think of. It's really a recasting of the relational methods, but, in our special style to get things down.
Minimum Collection Query Methods (Terms)
These two seem necessary, but may actually be more nice to have depending on your process.
The Model and the Actor
At this point we now have defined an object store. Most of us in way or another have built something like this, and do it all the time, but if you follow the above stricter twist, you can build declarative business rules on it. We can do this with actors and models. The one ground rule going in, is that, everything is an object in the store, and the model governs how they all play together. The actor keeps track of what each user is doing. In particular, it keeps track of selections, and that is used against the model.
Now, a model consists of four kinds of rules. There are rules for create, update, delete and select. A system is a model plus a collection. All things in the system are create, update, delete and select. All are derived in terms of selection.
Selection
Selection in particular, means, "the user can select an object". Selection management in a store is vitally important and yet often overlooked. Sometimes it is explicit, like, when a user selects objects on a PowerPoint slide or items in a shopping cart. Sometimes it is implicit, like, when a user selects a main menu by logging in, then, accesses in turn various modules to search for and get to an order, for example.
So, in your model, store rules for a selection in this way: "This class id may be selected, if this list of classes is previously selected, and, when this class id is selected, then, the actor's selection will have this list of classes cleared from their selection, and, you will also create objects of this list of classes." The last bit about creating objects on selection is useful for a couple of reasons - in particular, for audit, messaging, and so forth.
Creation
Armed with rules on how to select objects, you can then build the chain of rules of when to create them. The user is always doing something, so they always have a selection. So add to your model, a facility for creation rules. Write them like that. "This class id may be created, if this list of classes is previously selected, and, when a class is created, then, also create child objects of these classes." The latter bit is useful, because, in particular, since we said objects are flat, then, it's an easy way to get child objects that must be pre-existing for edit purposes.
Put in a spot for defaults for when the objects are created. Also, it is really handy to implement something that puts the fields from all the selected objects into the newly created object for the fields in that new object that match the fields in what's selected. The dictionary you built previously makes this a slam dunk. This facility, also, will get used for all the cases when that object is created.
Finally, add a spot on that rule for a list of queries that will be run and attached to the return result when an object of that class is selected.
Delete and Update
For completeness sake, also build out rules that say, for deletes and updates. For the updates, like the rule sets for create and update, you'll have the bit where it says, "If this object given these selections is valid, then, create an object of this class." Implement the create bit through the creation rule on the model, so, you get all the goodness of auto-create based on selections.
Constraints
You're probably thinking that you'll want some additional constraints, in the form of queries, based on our selection rules, and you know, you'd be right. But we're going to leave that go for a minute and come back to it. How's the Disney song go? "We don't talk about Bruno..." Oh, not that one. The older one. "Let it gooo." But, just for now.
The Service / API and the State Message
Now you have all the pieces in play to build your service. The good news is, the return message from anything you do will always be the same, but with a tweak or two, and that, you can ask any time. Conceptually, this return message, the state message, gives a list of things one is allowed to do as an actor, based on what that actor has selected, and the rules put forth in the modelling engine. Now let's build some methods.
Select Object
The select object message attempts to select an object. First get a handle to the model and pull that in. Use the model and collection to do some checking for validity. First, it checks for object ownership and existence. Is the object part of the collection? Is the rule that allows the selection of the object still valid on that actor?
If those are good, you can select the object. To select the object, add the object to the selection list on the actor. If the user is extending the selection, that's all you need. But, if the user is re-selecting, then, you'll check the model for the object being selected and see the list of classes whose selections must be cleared prior to that new selection. Implement the clear for those. That little method of checking base class matching comes in super useful here.
Then, now that you've updated the selection, you'll go ahead and check that list of objects to be created on select, and call the create API for each of those...
领英推荐
Finally, you'll call the GetActorState to return the revised state.
Create Object
The create object message attempts to create an object. Essentially, like select, it should conceptually always work in the single user case, but for multi-user and errant cases, you'll use the collection to make sure the object exists, and the actor to ensure the right things are selected.
Then, you'll create the object, automatically populating it with the primary key value, and the defaults as per the model and the selection.
Then, now that you've created the object, you'll go ahead and check that list of objects to be created on create, and call the Create API for each of those...recursively.
Finally, you'll call the GetActorState to return the revised state.
Update Object
Finally, we have the update object method. On update, we can create new objects as well. Some of those objects can be validation returns, which, we shall view in our next article.
And, then, again, we'll call GetActorState to return the revised state.
Get Actor State
So armed with our model, and actor, and the core of our easy business API, we can finally we work out our GetActorState method, that returns every message. Here's what your GetActorState method should do.
First, you'll take a look at the objects in the collection, and, for simplicity, we can assume you want to return a list of them all. You can filter that down quite a bit by looking at the model. Go through the model, combined with the actor selections, and include and stamp all records that either are already selected or can be selected, deleted, or updated in any way. Your model will naturally constrain the objects you are allowed to include, so its ok.
Then, go through all the create rules in your model, along with your actor selections, and from there, you'll get a list of objects that can be created.
The finished product would look something like this:
Actor State consists of:
That gives you everything you need to build a really a simple UI layer, and plays very nicely with the sort of rendering loops that people use in React or Angular, and is also quite amenable to native applications development - especially if you are using immediate mode rendering.
Putting It All Together
So now that you have your model, dictionary, actor definition and collection service, we can summarize.
Load a dictionary and classes.
Create a model and all the rules on it.
The dictionary and model can either come from a database or a file, or both, and present the interesting twists that as this configuration file changes, the entire schema built in the database by this model should also automatically migrate.
2. The service implements the methods above:
ActorState GetCurrentState()
ActorState CreateObject( actor, class id )
ActorState UpdateObject( actor, object )
ActorState SelectObject( actor, object )
ActorState DeleteObject( actor, object )
3. The user agent or application calls GetCurrentState initially.
4. The user agent or application renders the view, presenting the user the options in the actor state: things that can be selected, updated, created, etc.
5. When the user Creates, Updates, Selects, and Deletes, the application calls the service. The service evaluates follows the steps outlined above, and returns the state, and the whole process repeats with step 4 back to here.
Wrapping Up, Code, Low Code, No Code, Same Difference
Now we come to the flux of the matter: once you have the engine coded, you'll notice that the model and dictionary are simply data elements. You can implement those in code, pull them from a file, or put them in a database itself and edit that. From the backend perspective, it makes absolutely no difference where those things live, and the information content to your application remains the same regardless of its facade.
So, if you have a no code thing, you have a total visual experience with lots of paperclip icons to walk you through your application. If you have a low code thing, you perhaps have some sort of a semi-studio like experience with icons and docking bars and a visual preview that you type in a few lines of code for. If you have a code thing, you just have a single text file that you can edit with VS Code. In all cases, the application of the solution would require some degree of technical expertise and training, and either way, they are all editing the same ultimate content.
In my next article, I'll run through an actual example of this, using my own low code framework, built pretty much as above (like you can).