How Do Developers Test a Single Microservice Locally When an Application Has 10+ Microservices?
One of the most common questions developers have when learning microservices is: "If my application has 10–15 microservices running in Docker, and I need to modify only one…
One of the most common questions developers have when learning microservices is:
“If my application has 10–15 microservices running in Docker, and I need to modify only one service, do I need to run everything again? How does local development actually work?”
Let’s understand how this works in real enterprise projects.
A Typical Microservices Architecture
Suppose your application consists of the following services:
- API Gateway
- Config Server
- Eureka Server
- Payment Service
- Order Service
- Inventory Service
- User Service
- Notification Service
- PostgreSQL
- Redis
- RabbitMQ
In production, all these services may run inside Docker containers or Kubernetes Pods.
But during development, developers rarely run every service locally.
Step 1: Pull the Latest Code
Suppose you’re assigned a task to modify the Payment Service.
The first step is always:
git pull
After pulling the latest code, you make your changes in the Payment Service.
Now comes the important part—how do you test those changes?
Scenario 1: Payment Service Can Be Tested Independently
Sometimes the Payment Service only depends on infrastructure components like:
- PostgreSQL
- Redis
- RabbitMQ
- Config Server
- Eureka Server
In this case, you don’t need to run all other microservices.
Simply start the required infrastructure using Docker Compose:
docker compose up postgres redis rabbitmq configserver eurekaserver
Then run the Payment Service directly from IntelliJ or VS Code.
Payment Service (Local JVM)
│
├── PostgreSQL (Docker)
├── Redis (Docker)
├── RabbitMQ (Docker)
├── Config Server (Docker)
└── Eureka Server (Docker)
This is one of the most common development workflows.
Scenario 2: Payment Also Needs the Order Service
Now suppose Payment calls the Order Service using REST or OpenFeign.
Instead of running all services, developers usually:
- Start infrastructure in Docker.
- Run Order Service locally.
- Run Payment Service locally.
Docker
-----------------------------------
PostgreSQL
Redis
RabbitMQ
Config Server
Eureka Server
-----------------------------------
â–²
│
Order Service (Local JVM)
â–²
│
Payment Service (Local JVM)
Both services register themselves with Eureka.
Payment discovers Order through Eureka automatically.
Scenario 3: Only Payment Service Is Being Modified
This is probably the most common situation.
Suppose all services are already running in Docker.
You stop only the Payment container:
docker compose stop payment
Now run Payment from IntelliJ.
Payment Service (Local JVM)
│
â–¼
Order Service (Docker)
Inventory Service (Docker)
User Service (Docker)
Notification Service (Docker)
Everything else continues running inside Docker while you debug the Payment Service locally.
This gives you:
- Fast startup
- Easy debugging
- Breakpoints
- Hot reload
without restarting the whole application.
Scenario 4: Full End-to-End Testing
Sometimes you need to test the complete business flow.
For example:
Customer → Payment → Order → Inventory → Notification
In that case, you can run all services.
Then stop only the Payment container and run the Payment Service locally.
12 Services (Docker)
â–²
Payment Service (Local JVM)
This allows you to debug only the service you’re working on while keeping the rest of the application running.
A Common Confusion: What About URLs?
When developers first start using Docker, they often wonder:
“If my service runs locally, I use localhost. If it runs inside Docker, I use the Docker service name. Do I have to keep changing URLs?”
The answer depends on what you’re connecting to.
Connecting to Infrastructure
Suppose Payment needs PostgreSQL.
When Payment Runs Locally
Docker exposes PostgreSQL on your machine.
jdbc:postgresql://localhost:5432/paymentdb
Here, localhost refers to your own computer.
When Payment Runs Inside Docker
Inside a Docker container, localhost refers to the container itself—not your machine.
Instead, Docker provides built-in DNS.
You use the Docker service name:
jdbc:postgresql://postgres:5432/paymentdb
Here, postgres is the service name defined in docker-compose.yml.
Do Developers Manually Change These URLs?
No.
Enterprise applications avoid hardcoding environment-specific URLs.
Instead, they use one of these approaches:
- Spring Profiles
- Environment Variables
- Spring Cloud Config
- Kubernetes ConfigMaps and Secrets
For example:
application-local.yml
spring.datasource.url=jdbc:postgresql://localhost:5432/paymentdb
application-docker.yml
spring.datasource.url=jdbc:postgresql://postgres:5432/paymentdb
When running locally:
spring.profiles.active=local
When running inside Docker:
SPRING_PROFILES_ACTIVE=docker
Spring Boot automatically loads the correct configuration.
No code changes are required.
What About Service-to-Service Communication?
Suppose Payment calls the Order Service.
Many beginners think they need something like:
Local:
http://localhost:8082
Docker:
http://order:8082
Fortunately, modern Spring Boot applications don’t work this way.
Instead, they use OpenFeign together with Eureka (or Kubernetes Service Discovery).
Example:
@FeignClient(name = "orders")
public interface OrderClient {
}
Notice that there is no URL.
Feign simply asks the service registry:
“Where is the Orders Service?”
If Orders is running locally:
orders → localhost:8082
If Orders is running in Docker:
orders → order:8082
Feign automatically calls the correct instance.
Your application code never changes.
Does OpenFeign Eliminate All URL Problems?
Not completely.
OpenFeign solves only microservice-to-microservice communication.
Infrastructure components like:
- PostgreSQL
- Redis
- RabbitMQ
- Kafka
still require environment-specific configuration.
Fortunately, this is handled using profiles, environment variables, or centralized configuration, so developers rarely edit URLs manually.
Summary
| Component | Local Development | Docker/Kubernetes |
|---|---|---|
| Payment → Order Service | OpenFeign + Service Discovery | OpenFeign + Service Discovery |
| Payment → Inventory Service | OpenFeign + Service Discovery | OpenFeign + Service Discovery |
| Payment → User Service | OpenFeign + Service Discovery | OpenFeign + Service Discovery |
| Payment → PostgreSQL | localhost | Docker Service Name / Kubernetes Service |
| Payment → Redis | localhost | Docker Service Name / Kubernetes Service |
| Payment → RabbitMQ | localhost | Docker Service Name / Kubernetes Service |
Final Takeaway
Modern microservice development is designed to make developers productive.
When working on a single service, developers typically:
- Pull the latest code from Git.
- Run only the service they’re modifying in their IDE.
- Keep infrastructure components in Docker.
- Run additional microservices only if required.
- Use OpenFeign with service discovery for inter-service communication.
- Use profiles or environment variables for infrastructure configuration.
This workflow allows developers to debug quickly, avoid running unnecessary services, and work in an environment that closely resembles production—all without constantly changing URLs or configuration files.