Microservices-api-docker

In today's software world, applications are no longer built as one big block (monolith) where everything - UI, logic, and database - is tightly connected. Instead, developers use Microservices Architecture, where an application is split into small, independent services that work together like a team.

Each service does one job well, and all services communicate to form a complete system.

What You'll Learn in This Article

What are Microservices?

Microservices are a way of building software where an application is split into many small, independent services.
Each service:

How Microservices Communicate

Microservices need to talk to each other to work as a complete system. This communication usually happens in two main ways:

👉 Imagine a project team: Think of Microservices Like a Team of Developers

Microservices work the same way:

Why Microservices Matter

👉 In short:
Microservices = small, self-contained services working together as one big application.

How to Implement Microservices in .NET Core

Let's understand microservices with a developer team example.

Imagine you are building a project with three developers:

  1. UI Developer Service – focuses only on user interface.

  2. API Developer Service – manages data flow and backend APIs.

  3. Database Developer Service – takes care of storing and retrieving information.

Each developer works independently, but their work is combined to deliver the full project. Similarly, in .NET Core:

Step 1. Create Independent Services

👉 Just like each developer has their own tasks and tools.

Step 2. Define Communication

👉 Just like developers talk to each other through daily standups or messages to keep the project moving.

Step 3. Independent Deployment

👉 Just like one developer can push changes to their part of the code without waiting for the whole team.

Step 4. Bringing It Together

When all services are up and running:

👉 Exactly how three developers working independently ship one complete project. With this example, you see how microservices are like a team of developers, each focusing on one area, but collaborating through APIs to complete the application.

Microservices in .NET Core: A Developer Team Code Example

We will create three microservices:

  1. DbService – Database developer

  2. ApiService – API developer

  3. UiService – UI developer

Each service is independent, communicates via HTTP APIs, and uses Dependency Injection (DI) where needed.

DbService – Database Developer

// DbService/Controllers/DatabaseController.cs
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

namespace DbService.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class DatabaseController : ControllerBase
    {
        // Simulated database: dictionary stores task info
        private static readonly Dictionary<int, string> data = new()
        {
            { 1, "Task 1" },
            { 2, "Task 2" },
            { 3, "Task 3" }
        };

        // GET api/database/1
        [HttpGet("{id}")]
        public IActionResult GetData(int id)
        {
            // Check if the requested task exists
            if (data.ContainsKey(id))
                return Ok(new { Id = id, Task = data[id] });

            // If task not found, return 404
            return NotFound("Task not found");
        }
    }
}

Comments / What is happening inside:

ApiService – API Developer

// ApiService/Controllers/TaskController.cs
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace ApiService.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class TaskController : ControllerBase
    {
        private readonly HttpClient _httpClient;

        // DI injects HttpClient so service can call DbService
        public TaskController(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        // GET api/task/1
        [HttpGet("{id}")]
        public async Task<IActionResult> GetTask(int id)
        {
            // Call DbService to get task info
            var response = await _httpClient.GetAsync($"https://localhost:5001/api/database/{id}");

            // If DbService returns error, propagate 404
            if (!response.IsSuccessStatusCode)
                return NotFound("Task not found");

            // Read JSON response from DbService
            var json = await response.Content.ReadAsStringAsync();

            // Deserialize JSON into dynamic object
            var task = JsonSerializer.Deserialize<dynamic>(json);

            // Return to caller (UiService or client)
            return Ok(new { Message = "Fetched by API Service", Task = task });
        }
    }
}

Comments / What is happening inside:

UiService – UI Developer

// UiService/Controllers/ViewController.cs
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace UiService.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ViewController : ControllerBase
    {
        private readonly HttpClient _httpClient;

        // DI injects HttpClient so service can call ApiService
        public ViewController(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        // GET api/view/1
        [HttpGet("{id}")]
        public async Task<IActionResult> ShowTask(int id)
        {
            // Call ApiService to get task info
            var response = await _httpClient.GetAsync($"https://localhost:5002/api/task/{id}");

            // If ApiService returns error, propagate 404
            if (!response.IsSuccessStatusCode)
                return NotFound("Task not found");

            // Read JSON response from ApiService
            var json = await response.Content.ReadAsStringAsync();

            // Deserialize JSON into dynamic object
            var task = JsonSerializer.Deserialize<dynamic>(json);

            // Return final result to user
            return Ok(new { Message = "UI Service showing task", Task = task });
        }
    }
}

Comments / What is happening inside:

How the Flow Works (Developer Analogy)

ServiceDeveloper RoleContainer Analogy
DbServiceDatabase DeveloperWorkstation with all data ready
ApiServiceAPI DeveloperWorkstation that asks DbService for data
UiServiceUI DeveloperFetches data from ApiService

Each service runs inside its own container, isolated but able to communicate with other services.

  1. User calls UiService → wants to see Task 1

  2. UiService calls ApiService → asks for Task 1

  3. ApiService calls DbService → fetches Task 1 data

  4. DbService returns data → ApiService → UiService → User

Like three developers working independently but collaborating through defined channels to deliver the project.

Containerization with Docker – Let Each Developer Work Independently

In a microservices setup, each service (UiService, ApiService, DbService) runs independently, like each developer in a team having their own workstation.

Containerization packages each service with everything it needs — code, runtime, and libraries — into a container, so it runs the same way everywhere, whether on your laptop, testing server, or production.

Why Containerization Matters

Dockerfile Implementation for Each Service

DbService – Database Developer

# Build stage: setup and compile code
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

# Runtime stage: ready-to-run service
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=build /app/out .

EXPOSE 5001
ENTRYPOINT ["dotnet", "DbService.dll"]

What's happening:

ApiService – API Developer

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=build /app/out .

EXPOSE 5002
ENTRYPOINT ["dotnet", "ApiService.dll"]

What's happening:

UiService – UI Developer

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /app

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o out

# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=build /app/out .

EXPOSE 5003
ENTRYPOINT ["dotnet", "UiService.dll"]

What's happening:

Running All Services Together

# Build Docker images
docker build -t dbservice:1.0 ./DbService
docker build -t apiservice:1.0 ./ApiService
docker build -t uiservice:1.0 ./UiService

# Run containers
docker run -d -p 5001:5001 --name dbservice dbservice:1.0
docker run -d -p 5002:5002 --name apiservice apiservice:1.0
docker run -d -p 5003:5003 --name uiservice uiservice:1.0

Explanation:

Flow Recap (Developer Team Analogy)

  1. User -> UiService: UI Developer gets the request.

  2. UiService -> ApiService: UI Developer asks API Developer for data.

  3. ApiService -> DbService: API Developer asks Database Developer for data.

  4. DbService -> ApiService → UiService → User: Data flows back, UI Developer shows it to the user.

Key Takeaway

Containerization with Docker lets microservices run independently, stay isolated, and scale easily, while still collaborating — just like a team of developers delivering a complete project together.

Conclusion – Microservices with Developer Team Analogy

In this article, we explored how microservices in .NET Core help build scalable, maintainable, and resilient applications. By using a developer team analogy, we understood that:

Microservices architecture not only improves the scalability and flexibility of your applications but also reflects real-world enterprise practices, making it an essential skill for modern .NET developers.

Next Steps

Now that you've understood microservices in .NET Core, here's what you can focus on next to strengthen your skills:

  1. API Integration & RESTful Services

  2. Authentication & Authorization (JWT, OAuth2, RBAC, ABAC)

  3. Asynchronous Programming & Multithreading (async/await, Task)

  4. Unit Testing & Mocking (xUnit/NUnit)

  5. Logging & Monitoring (Serilog, Application Insights)

  6. Basic Cloud & Deployment (Azure/AWS, CI/CD pipelines)

Pro Tip: Combine coding practice with conceptual understanding, and use small projects (like your developer team example) to demonstrate your knowledge to others.

Thank you for reading my article! 🙏
I hope this helps you understand microservices in .NET Core, Dependency Injection, API communication, and Docker in a simple, practical way.