Blog

  • The Real Benefit of Layered Architecture: Replaceable Components

    Intro

    Think about the capabilities that our application depends on to accomplish specific functions, like:

    1. File Storage
    2. Authentication Provider
    3. AI Provider

    We need file storage to save uploaded files. We need authentication provider to implement secure login system, and we need AI provider, if we want to integrate AI functionalities.

    But each one of the capabilities, has options to choose from.
    For example:

    1. File Storage (Amazon S3? MinIO? Or even Local storage?)
    2. Authentication Provider (Okta? Microsoft Entra ID or even self-serve?)
    3. AI Provider (OpenAI, Anthropic, Grok)

    Wouldn’t it be nice, if we could switch between those options with simple configuration setting while developing?

    Imagine that Amazon S3 is hard-coded into our Business Logic. If we want to change the storage vendor to some other vendor, we will need to fix the whole entire business logic to handle the newly selected vendor.

    That’s a hassle. Not easy to maintain.

    So just as we separated all the functionalities within an app into components(frontend, backend, worker) for maintainability, we also separate Business Logic and capability, so we can better easily maintain our app.

    If we want to change the storage vendor, no problem, just fix the config file and the Factory Pattern will create appropriate implementation, return that instance and send it back to Business Logic. No need for Business Logic editing, because that’s the whole point of separating the two.

    In Cloud Native architecture, this capability is represented by Abstraction. Abstraction represents a capability that Business Logic depends on to accomplish specific tasks. We need many capabilities like AI, Storage, Authentication, Queues etc, and they’re all Abstractions.

    Implementation = Actual Service That Will Be Used For Abstraction

    Common Abstractions in an app include:

    • Storage
    • AI Provider
    • Authentication
    • Queue

    And each Abstraction, has implementation options to choose from, for example:

    • Storage(Amazon S3, MinIO, LocalStorage)
    • AI Provider(OpenAI, Anthropic, Grok)
    • Authentication(Okta, Microsoft Entra ID)
    • Queue(Redis etc.)

    Two keywords to remember so far. Abstraction and Implementation.
    Abstraction is a capability. An Implementation is the concrete service that fulfills an Abstraction.

    How Abstraction Works

    Now we know about Abstraction and Implementation, let’s go over the workflow how Business Logic receives Implementation so it can work with it.

    Business Logic
    (I need Storage to save files!)

    Abstraction
    (Storage capability is available!)

    Interface
    (Implementation will support these actions: save, delete, open)

    Read Configuration
    (Read config file and find out which Implementation(“Amazon S3” “MinIO” or “LocalStorage”) is set for the project.)

    Factory Pattern
    (Creates implementation instance according to the config setting.)

    Dependency Injection
    (Inject implementation instance created in Factory Pattern to Business Logic)

    Business Logic
    (Implementation received and can work with it)

    Let me explain each step in more detail below.

    Business Logic

    For example, Business Logic wants to save an uploaded PDF to a Storage service. So the business logic states that it needs to depend on the Storage abstraction, to accomplish the save action. So basically Business Logic will say out loud: I need Storage!!

    Another piece of business logic might need Authentication, or AI or any other capability that its required to accomplish a task. (All of them, Abstraction)

    Abstraction

    A capability Business Logic depends on. (Storage, Authentication, AI etc.)

    Interface

    A new term here. Interface. An Interface defines the operations that every implementation of Abstraction must support.

    Whether the implementation is Amazon S3, MinIO, or LocalStorage, they must all provide the same operations like save, delete, and open.

    Implementation

    An actual service that fulfills the interface contract.

    For example for Storage abstraction have implementation choices to choose from:

    1. Amazon S3
    2. MinIO
    3. LocalStorage

    And each implementation provides the same Interface(save, delete, open).

    Factory Pattern

    The application reads the configuration file to find out which implementation is set, and creates implementation instance which will be later sent back to Business Logic.

    This process of creating implementation instance, is called Factory Pattern.

    Factory Pattern Steps

    1. Reads config
    2. Create implementation
    3. Returns its instance

    (config files like .env, ConfigMap, compose.yaml etc.)

    Dependency Injection

    Once we have the implementation instance ready, it will finally inject the instance to Business Logic so it can work with the instance. And this action is called Dependency Injection. Dependency(Storage implementation instance) will be injected, to Business Logic.

    Why Does This Separation Matter?

    Because if we break down responsibilities to least common denominator, it will be so much easier for us to maintain an application.

    By separating responsibilities, we now can:

    1. Replace infrastructure by changing config files.
    2. Upgrade the program of each responsibility easier.
    3. Maintain and test the program easier.

    Final Thoughts

    Cloud Native application design is about breaking down all the responsibilities into smaller units of responsibilities so applications become easier to maintain, scale and evolve.

    Just as Separation of Concerns concept supported the following areas, Abstraction also supports the following:

    • Replaceability
    • Maintainability
    • Testability
    • Flexibility
  • Cloud Native Isn’t About Docker or Kubernetes

    Intro

    Quick introduction, my name is Shoki, self-taught self-claimed web-developer. My experience prior to this cloud native app learning, I’ve only built web app in a monolithic way, using Laravel.

    As a long-time web-tech lover, I’ve always wanted to learn about cloud native app development, but since I didn’t have a chance to build one, as I would only learn about tech stack that I need for my client work, and I never had a cloud native app dev work for client since I always and almost worked with SMB’s, as they don’t need or even have a budget to create cloud native app and if they do have a budget, they have no reason to hire ME for it, since I didn’t claim my self to be cloud app developer at that time.

    But here I am, as if I am asking my self for a client work: Learn about Cloud Native Application Development

    Learning about Cloud Native App

    Enough for my excuses not learning about cloud native app till now, but nothing is never too late. Let’s jump right in.

    Docker? Kubernetes?

    As a monolithic only experience web developer myself, I knew I wasn’t cloud native but acted like one. I only KINDA KNEW terms like Docker, Container and Kubernetes. And I thought learning about Docker, Container and Kubernetes are the way to learn about cloud native apps.

    Because to us like monolithic developers, we somehow believe that cloud native app are all about Docker, containers and Kubernetes. I even thought I can turn monolithic app into cloud native app by utilizing Docker and Kubernetes.

    But it turns out, IT IS NOT ABOUT DOCKER CONTAINERS OR KUBERNETES. They are just one “CONCERN” or “Functionality” if you will, that they are responsible for.

    So Docker, is for example, responsible for managing containers. Kubernetes is responsible for load-balancing.

    And these concept of responsibilities, is in cloud native app development, is called “Separation of Concerns.”

    Cloud Native App is about Separation of Concerns(Separation of Responsibility)

    So within a monolithic app(in Laravel), we had like Routes, Controllers, Models, Views, Migrations.
    And each one of them, had a responsibility to do something.

    For example:

    1. Routes (creates API endpoints)
    2. Controllers (receives API Calls to run methods, talk to database, receives data from database, and send those data to views)
    3. Models (defines database schema)
    4. Views (receives data from controllers, send form data to controllers, provide and show data to user)

    I would put all of these responsibilities or functionalities in one Laravel app, upload and deploy the git repo to EC2 instance, and I used to call this cloud app lol. Only because I uploaded onto AWS cloud I called it cloud app lol.

    But this isn’t cloud native app! This is a monolithic app (all the concerns in one package).

    However, Cloud Native app isn’t much far away from monolithic app architecture. We just think in a cloud native way and it fits right in.

    Cloud Native app just separates all possible concerns into individual section called containers (I used to think I should put the whole app into containers lol).

    Which is the core concept on Cloud Native app development, called the “Separation of Concerns.”

    Let’s build one (simulation)

    Best way to learn something, is by doing. So instead of me listing up all the concerns within cloud native app, let’s simulate a cloud native app dev from scratch.

    First thing first, let’s think of an app structure. Forget about cool cloud concepts ya’ll want to start learning right way and for now, I’m gonna focus on just an app itself. (App dev itself is the dish but we are designing and building the whole restaurant)

    So the app itself usually contains these two components.

    • Frontend
    • Backend

    We will break down each component into more details, but before we do that, let’s define each component’s core responsibility.

    Frontend: is responsible for views, which is the app interface.
    Backend: is responsible for systematic functions, such as manipulating data.

    And each component consists smaller responsibilities.

    Frontend

    • HTML/CSS
    • Handle user interactions (forms)
    • Sending data to backend
    • Retrieve data from backend

    Backend

    Backend has so many more responsibilities compared to frontend.

    • Defining routes(API endpoints)
    • Configurations
    • Retrieve user input from frontend
    • Database schema
    • Database models
    • Save data to database
    • Get data from database
    • Communicate with storage
    • Save files to storage
    • Get files from storage
    • Business logic(data manipulation)
    • Assign work to worker component(you will learn about worker later)

    I might have missed some responsibilities but within each component has many responsibilities to meet requirements, to be a successful team, to accomplish team goal.

    So we have two big departments right now: Frontend and Backend. And a department, is called in cloud native app context, a Component.

    And within each department(component), has many responsibilities. And we assign each responsibility to different teams. And the team is called in cloud native app context, a Layer.

    • App
      • Frontend(Component)
        • HTML/CSS(Layer)
        • Handle user interactions (forms) (Layer)
        • Sending data to backend (Layer)
        • Retrieve data from backend (Layer)
      • Backend(Component)
        • Defining routes(API endpoints) (Layer)
        • Configurations (Layer)
        • Retrieve user input from frontend (Layer)
        • Database schema (Layer)
        • Database models (Layer)
        • Save data to database (Layer)
        • Get data from database (Layer)
        • Communicate with storage (Layer)
        • Save files to storage (Layer)
        • Get files from storage (Layer)
        • Business logic(data manipulation) (Layer)
        • Assign work to worker component (Layer)

    And all of the above I’ve been talking about, is in cloud native app dev context, is the core concept of cloud native app architecture, which is the Layered Architecture concept.

    Everything is separated into micro layers to have minimal responsibility for each teams. And this is pretty much is the Separation of Concerns concept.

    So two big terms we learned today: Layered Architecture, and Separation of Concerns. Two big cloud native app concepts.

    We need to understand “Layered Architecture” concept, because this is the reason we make an app cloud native in the first place: Highly maintainable. We need to know where everything is, in order to be highly maintainable.

    Note: 3 Cloud Highly’s: Highly maintainable, Highly available, and Highly scalable.

    “Separation of concerns” concept is supported by “Layered Architecture” concept. So in “Layered Architecture” concept, we learned that we’re breaking down programs into small pieces, so it’s easy to know what’s happening within an app.

    But what’s the other reason do you think we use “Layered Architecture?” It’s because we need to separate responsibility. If we separate responsibility, we can easily find errors and fix errors, as we can quickly navigate to errors based on layers.

    Okay, that’s it for this article!

    Here is a key points discussed in this article:

    1. Cloud native app isn’t about learning Docker or Kubernetes
    2. Cloud native app is about breaking down big programs into small pieces. Breakdown to Components to Layers. (Layered Architecture concept)
    3. Cloud native app is about separating responsibilities into smaller teams. We breakdown components to layers, so we can separate responsibility. (Separation of Concerns concept)
    4. Docker and Kubernetes are the tools used in cloud native app architecture, to help you with availability and scalability.
    5. Cloud native 3 highlys: Maintainable, Available, Scalable.