As developers, we often map business processes to digital processes, from sending an email to something quite complex. Let's look at how to take a more complicated process and write clean and elegant code.
It all starts with a workflow. I tweeted about writing this tutorial to see if there would be any feedback on business processes people would find helpful - I only really got one response, though.
So with that in mind, let's look at the Order/Shipping process, something with enough moving parts to get the idea across - but I won't go into too much detail from a domain logic perspective.
Imagine you run an online merch store, have an online shop, and use a dropshipping service to send merch out on demand when an order is placed. We need to think about what the business process might look like without any digital help - this allows us to understand the business and its needs.
An item is requested (we are using a print-on-demand service, so stock isn't an issue).
We take the customers' details.
We create an order for this new customer.
We accept payment for this order.
We confirm the order and payment to the customer.
We then place our order with the print-on-demand service.
The print-on-demand service will periodically update us on the order status, which we can update our customers, but this would be a different business process. Let's look at the order process first and imagine this was all done inline in one controller. It would get quite complicated to manage or change.
So if we walk through this code, we see that we create a user and order - then accept the payment and send an email. Finally, we add a status message to the session and redirect the customer.
So we write to the database twice, talk to the payment API, send an email, and finally, write to the session and redirect. It is quite a lot in one synchronous thread to handle, with a lot of potential for things to break. The logical step here is to move this to a background job so that we have a level of fault tolerance.
We have cleaned up our controller a lot - however, all we have done is move the problem to a background process. While moving this to a background process is the right way to handle this, we need to approach this a lot differently.
Firstly, we want to first or create the customer - in case they have made an order before.
Our next step is to move the creation of a customer to a shared class - this is one of many times we would want to create or get a customer record.
Let's look at the background process code if we moved it directly there.
Not too bad, but - what if a step fails and we retry the job? We will end up redoing parts of this process again and again when not needed. We should first look to create the order within a database transaction.
Now we can update our background process to implement this new command.
This approach works well. However, it isn't ideal, and you do not have much visibility at any point. We could model this differently so that we are modeling our business process instead of splitting it out into parts.
It all starts with the Pipeline facade, enabling us to build this process correctly. We will still want to create our customer in the controller, but we will handle the rest of the process within the background job using a business process.
To begin with, we will need an abstract class that our business process classes can extend to minimize code duplication.
Our business process class will have many associated tasks, which we declare in the implementation. Then our abstract process will take the passed-on payload and send it through these tasks - eventually returning. Unfortunately, I can't think of a nice way to return an actual type instead of mixed, but sometimes we have to compromise...
As you can see, this is super clean to look at and works well. These tasks can be reused in other business processes where it makes sense.
Our background process now tries to handle the business process, and if any exceptions happen, we can fail and retry the process later on. As Laravel will use its DI container to pass through what you need into the jobs
handle method, we can pass our process class into this method and let Laravel resolve this for us.
Our business process tasks are invokable classes that get passed the "traveller", which is the payload we want to pass through, and a Closure which is the next task in the pipeline. This is similar to how the middleware functionality works in Laravel, where we can chain on as many as we need, and they are just sequentially called.
The payload we pass in can be a simple PHP object we can use to build as it goes through a pipeline, extending it at each step, allowing the next task in the pipeline to access any information it needs without running a database query.
Using this approach, we can break down our business processes that aren't digital and make digital representations of them. Chaining them together in this way adds automation where we need it. It is quite a simple approach, really, but it is very powerful.
Have you found a nice way to handle business processes in Laravel? What did you do? Let us know on twitter!