Exploring loosely coupled architecture – Understanding Event-Driven Architecture
Exploring loosely coupled architecture
Alright, in a vacuum, loosely coupled architecture seems like a bad idea. You disperse your components so much that there is no rhyme or reason as to how any information gets from one place to another. You can’t count any sort of consistent time for all your data to collate into one place for the thing you want to happen to actually happen.
However, there are a few factors that make loosely coupled architecture so effective in a practical setting. These factors are both philosophical and architectural. Firstly, no matter how well you design a system, it will fail somewhere at some time. The loosely coupled architecture allows the system to fail gracefully and to recover from failures in a way that doesn’t affect other components and users of the system. Because each component is isolated, these components can be identified after a single failure (a lot of the time, a clone component will succeed). This failure can be logged and detected and the correct parties can be notified without any interruption to the system. The failed component will not disrupt activities and it is not considered to be a bad thing. In fact, failure teaches us the weaknesses and shortcomings of the system, which can then be worked on quickly because you are only working on that isolated component.
The next factor comes from availability. Loosely coupled architecture offers small components that are replicated for each individual use. Now, you would say that this is a limit in and of itself since even if you can divide the resources between users, there won’t be enough resources to go around. In the past, this would have been true, but with modern applications running on the cloud, there can be infinite provisioning for services that support a loose architecture. You can handle the volume of resources effectively because the scale for the services that can run this kind of architecture is nearly infinite. This results in an environment where an architecture that will provision based on use becomes the optimal architecture. A more tightly coupled architecture might suit more limited means, but that is not the case for scenarios with flexible resources and unknown loads.
Finally, the last factor that puts loosely coupled architecture in the lead is laziness. Yes, laziness. I have found in my life that the leading cause behind my laziness is not the fact that I don’t want to do something, but rather because my brain is overwhelmed with useless information about something that I might want to do. I started actually getting somewhere when instead of trying to figure out these minutiae in a way that was ineffective and useless, I just started doing things and figuring them out as I went along. That’s basically why loosely coupled architecture is good. There are fewer things to worry about and it is easier to work on. Instead of worrying about every single little thing before you even start implementing the system, you can just start implementing and worry about optimization later. This is perfect for someone like me who uses this approach for practically everything, and it’s the same for some of the biggest companies in the world as well. If you’ve heard of the Toyota way, it essentially follows the same principle: making mistakes and learning from them to get better. You can look it up; I encourage you to. But, in conclusion, this type of architecture is for the lazy, pragmatic developer who is just trying to get somewhere.
For the past several paragraphs, you have endured my philosophical rants, and now we get to the more pragmatic part where I show you stuff and try to reinforce what I ranted about. So, that is what we’re going to get into right now. We are going to make a basic application (just a lambda function, actually) that is triggered when an image is uploaded into an S3 bucket, takes the image and resizes it to a standard size, deletes the original image, and replaces it with the resized one:
import osimport tempfileimport boto3from PIL import Images3 = boto3.client(‘s3’)def lambda_handler(event, context): # Get the name of the bucket and the image name when upload is triggered. bucket = event[‘Records’][0][‘s3’][‘bucket’][‘name’] key = event[‘Records’][0][‘s3’][‘object’][‘key’] new_width = 300 #width of image new_height = 200 #height of image with tempfile.TemporaryDirectory() as tmpdir: # Download the original image from S3 into a pre-defined temporary directory download_path = os.path.join(tmpdir, ‘original.jpg’) #download the S3 file into the temporay path s3.download_file(bucket, key, download_path) with Image.open(download_path) as image: image = image.resize((new_width, new_height)) # Save the resized image in its own path resized_path = os.path.join(tmpdir, ‘resized.jpg’) image.save(resized_path) # Upload the resized image back to the S3 bucket and delete the original s3.delete_object(Bucket=bucket, Key=key) s3.upload_file(resized_path, bucket, key) return { ‘statusCode’: 200, ‘body’: ‘You don’t really need this because its not for people!’ }
This is a simple code for a very simple yet important function. The trigger for the image conversion can be placed either at the lambda function or the S3 bucket itself. If you have ever used one of those online services that convert your PDF to a Microsoft Word document or convert your WAVs into MP3s, they basically run on this concept. Even with a very minimal interface, they can be very effective and quite popular.
Perhaps before reading this book, you may have had the misconception that building some of these services might be difficult. In the world we live in, they are not. Once we have opened these horizons, everything becomes clearer, and one of the things that becomes clearest of all is the ability to move on from the old inefficient ways into newer, simpler ways. Let’s look at the path to that transition.