paint-brush
Commands & Events instead of CRUD — Part 1: Commands by@sven
3,181 reads
3,181 reads

Commands & Events instead of CRUD — Part 1: Commands

by Sven WiegandDecember 11th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

A lot of applications — especially in the enterprise area — implement entity manipulation mainly based on the CRUD operations. CRUD is an acronym for

Company Mentioned

Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Commands & Events instead of CRUD — Part 1: Commands
Sven Wiegand HackerNoon profile picture
A lot of applications — especially in the enterprise area — implement entity manipulation mainly based on the CRUD operations. CRUD is an acronym for
  • Create
  • Read
  • Update
  • Delete
These operations seem to fit the classical form based entity operations: Somewhere in the UI you click an “Add”-button, a form allows you to capture data (e.g. a new contact in a CRM system), you hit save and the new entity is created in the database. When you want to edit an entity nearly the same form is shown, but prefilled with the entity’s data from the database. You can modify the fields, hit “Save” and the modified entity is persisted.

Typical Problems of CRUD

From the implementation’s perspective in both situations (creation and update) an entity object of the same class is send around. Even if the user only changes the phone number of a contact, the whole entity is updated in our database. For rapid prototyping and very basic applications this might be a good approach as it allows a quick implementation. CRUD operations are natively supported by many frameworks. They can be easily implemented based on a relational database using abstraction layers like JPA or Hibernate and there are also tech stacks like Ruby on Rails or Grails which make implementing CRUD operations very fast. But in most products business needs grow over time and — as you will see — CRUD is very limited in its capabilities . The following list isn’t complete, but it shows typical requirements and problems I’ve encountered multiple times in my developer life.

Problem 1: Large EntityClasses with lots of optional Properties

Business objects which look simple in the beginning of a product’s lifecycle grow over time. Simply look at a contact object in a normal CRM application: What looks easy and small in the beginning becomes a monster class with dozens of properties over time spreading multiple tabs in a UI. Often there are views which only show a subset of your monster entity object. What do you want to do with the unrelevant data here?
  • Do you want to include all the data in the object send to the UI?
  • Do you want to create new specialized data transfer objects?
  • Or do you want to use your standard entity object and leave the not-required fields unset (aka null)?
This becomes even more interesting if you want to save the changes the user has made in the input form: Which fields do you want to transfer? Everything which is shown in the form or only what the user edited? And how should your low-level services know wich fields they can expect to be set and which are okay to be left empty?

In most applications I’ve seen a mix of slightly specialized objects and setting fields to null has been used. The developers did not wanted to create specialized objects for all use cases. Partly because there was no clear rule when to introduce new data transfer objects and partly because they had no idea how to name these new classes.

As a result over time it becomes hard to update the service layer. There is no clear logic when which data transfer objects are used and which fields of the used objects can be expected to be set. This leads to tons of not-null-checks in null-safe languages or more likely and even worse to many NullPointerExceptions.

Problem 2: Implementing Undo

In most products earlier or later the wish for undoing changes is raised. Implementing this in a CRUD-based application is at least difficult.

Problem 3: Providing a Change History

A requirement which often arrives later in a product’s lifecycle is the possibility to provide a change history for important entities to answer the question who did when change which properties of the entity? Implementing this in a classical CRUD-application is possible but complex.

Problem 4: Implementing Collaboration

Things really become challenging if you want to provide users the possibility to collaboratively work on objects. In most CRUD applications the latest change overwrites previous changes made en parallel by other users.

I don’t know how often I had to deal with this issue in my life already. As a last resort most teams implement some kind of locking to prevent parallel editing of business objects. But think of a tool like  — a simple Kanban-board solution — and how useless it would be if you couldn’t collaborate with your colleagues on one card at the same time…

Commands to the Rescue

Think about how you would implement something like Trello using only entity objects and CRUD. You might say that Trello is an extreme example, as collaboration is at its core, but is it really? Think about the (enterprise?) application you are currently working on and about how cool it would be if it would provide real collaboration, undo, … So lets take a look at commands and how they can help us to make our app more appealing and our code clearer and less error prone.

Declaring Commands

A command is an object that describes a request to do something and contains exactly the information required to perform an action of one specific type.

Commands and events are most fun in languages which provide syntax for one-line data classes like for example Kotlin or Scala. So lets take a look at how commands for working with cards on a Kanban board might look in Kotlin:
// Commands.kttypealias UserId = Stringinterface Command {    val user: UserId}// CardMessages.kttypealias CardId = Stringdata class CardPosition(val column: ColumnId, val before: CardId?)sealed class CardCommand : Command {  abstract val card: CardId}data class CreateCard(  override val user: UserId,   val board: BoardId, val pos: CardPosition,   override val card: CardId, val title: String) : CardCommand()data class RenameCard(  override val user: UserId, override val card: CardId,   val currentTitle: String, val title: String) : CardCommand()data class MoveCard(  override val user: UserId, override val card: CardId,   val currentPos: CardPosition, val pos: CardPosition) : CardCommand()data class DeleteCard(  override val user: UserId, override val card: CardId) : CardCommand()
Don’t care about the Kotlin details here like sealed classes and type declarations — I’ll explain them in another post. From the code above you can derive some of the characteristics of commands:
  • A command contains all information required to execute the requested operation.
  • A command contains all information required to validate the requested operation.
  • A command is a lightweight one time immutable object. You create it when you need it and drop it afterwards.
When working with commands created by the client it is best to work with decentralized ID creation (UUIDs). This avoids problems like for example double creation of entities.

Command Validation

I can’t stress often enough, that commands are requests to do something. Thus a command needs all information required to validate whether the request is valid and can be processed. This is also a benefit compared to sending around entities: Entities contain only their data — data required for validation must be collected from somewhere else.

From the code above you can see for example, that we’ve included information about the user who submitted the command. This allows us to verify, if the user is allowed to execute this request or dismiss it otherwise.

You may wonder about the currentTitle property of the RenameCard command: This allows us to verify, that the client (GUI, app) who send the command is in sync. If the currentTitledoes not match the card's real current title as known by the server we might want to reject the request.

Processing Commands

Now let’s take a look at how to process commands. Assume that they are rooted to a central command receiver which then forwards them based on their type to specialized receivers:
// CommandReceiver.kt...fun receive(cmd: Command) = when (cmd) {  is BoardCommand -> boardCommandReceiver.receive(cmd)  is ColumnCommand -> columnCommandReceiver.receive(cmd)  is CardCommand -> cardCommandReceiver.receive(cmd)  ...  else -> Unit}...
The specialized command receiver implements our business logic. It validates the command and rejects it if necessary. If the validation is successful it mutates our entity’s state:
// CardCommandReceiver.kt...fun receive(cmd: CardCommand) = when (cmd) {  is CreateCard -> validateAndCreateCard(cmd)  is RenameCard -> validateAndRenameCard(cmd)  is MoveCard -> validateAndMoveCard(cmd)  is DeleteCard -> validateAndDeleteCard(cmd)} ...private fun validateRenameCard(cmd: RenameCard) {  val user = getUser(cmd.user) // throws if user doesn't exist  val card = getCard(cmd.card) // throws if card doesn't exist  if (!user.canModify(card))    throw NotAllowedException()  if (card.title != card.currentTitle)    throw IllegalStateException("Concurrent change")  cardDao.renameCard(card.id, cmd.title)}...

Problems solved yet

Lets check which of the above mentioned problems we’ve solved until now.

We no longer deal with large entity objects with lots of optional fields. In the end we can work with one card object representing our card for displaying it (more on this in the next part). As soon as we want to mutate our card we don’t use the entity class at all. Instead we send tiny, specialized, immutable command objects exactly describing our intend. For a single command it is totally clear to our service layer (our command receivers) which properties of the command can be expected to be not-null, reducing the number of NullPointerExceptions (and totally eliminating them as we use Kotlin here).

Regarding our second problem we haven’t yet implemented undo, but it becomes as simple as that:

fun undoCommandFor(cmd: CardCommand) = when(cmd) {  is CreateCard ->     DeleteCommand(cmd.user, cmd.card)  is RenameCard ->     RenameCard(cmd.user, cmd.card, cmd.title, cmd.currentTitle)  is MoveCard ->     MoveCard(cmd.user, cmd.card, cmd.pos, cmd.currentPos)  is DeleteCard -> ...               }

See how easy it is to invert most of the commands to undo them. Just the DeleteCard-command isn't that easy, as you really would need to restore the whole object...

It would also be quite easy to implement a change history based on the successfully executed commands, but this will become even easier based on events as you might see in the next post.

Same is true for collaboration: We’ve already made things much better here as concurrent changes will no longer override each other. Instead of writing a whole object when editing its title we request our service layer to just rename the title. If another user moves the card in the same moment, both changes will succeed independent of each other. If two users would edit a card’s title the first one would win and the request of the second user will fail with an IllegalStateException as the command's currentTitle parameter wouldn't be "valid" anymore.

But we still haven’t solved the collaboration topic fully: We need to find a way to send the status updates back to all clients. Commands are not perfect here as they are requests and we need to communicate facts to the clients instead. More on this in part 2…

New Problems created

Commands are no silver bullet and create new problems on their own. The biggest challenge is, that you need to find the correct level of granularity. It’s obvious, that we cannot create commands to update each single property of an entity.

Conclusion

Thinking about commands is a great way of thinking about the use cases we want our application to support

Commands express fine-grained intends of what a user wants to change in our system. Instead of sending whole entity objects (or even worse: partial ones with lots of null-properties resulting in NullPointerExceptions) we send tailor-made one-shot objects exactly describing the user's request.

Our command receivers implement the business logic by validating the commands and applying them to our persisted data in case they are valid. The commands contain all information required for validation and execution. Thus our code becomes very clear as processing commands always follows the same pattern. Implementing undo functionality has become mostly trivial because generating inverted variants of commands is easy in most cases. And finally commands provide a good foundation for implementing a change history and collaborative editing. In the next part we will take a look at events and learn how they are related to commands and how we can benefit from them.
바카라사이트 바카라사이트 온라인바카라