Service Locator Pattern
1. What is Service Locator Pattern?
The Service Locator pattern is a JEE Business Tier Pattern. It separates JNDI/registry lookup code from the BusinessDelegate class and keeps it in a dedicated class that maintains a buffer/cache of external service component references. The Service Locator contains JNDI lookup code to locate and get external comp references from the registry.
One line: A centralized registry lookup class that caches external service references to avoid repeated costly JNDI lookups across multiple requests.
Local Services vs External Services
| Type | Description | Examples |
|---|---|---|
| Local Services | Service logics used only inside the project. BusinessDelegate class logics are local services. | Flipkart's billing calculations, discount logic, cart operations |
| External Services (Distributed Comps) | Service logics that can be accessed and used from different projects. Accessed over the network using distributed technologies. | GPay, PhonePe, BharatPe, ICC Score Comp, BSE Comp, WeatherReport comp |
How external services are discovered
To make our logics/services as external services or distributed comps, we need to take the support of Distributed Technologies like EJB (old), RMI (old), WebServices (SOAP or REST based).
The distributed comps/external comps keep their comp details in special registries to expose the details to different local or remote client projects. The special registries are like:
- JNDI Registry (Java Naming and Directory Interface)
- RMI Registry
- UDDI Registry (for SOAP WebServices)
The remote or local Client Apps/Projects search for external services/distributed comp in JNDI registry or UDDI registry and get references from those registries in order to consume the services of external service or distributed comp.
Real-world examples of client-service relationships
- Flipkart, Amazon, Myntra, Snapdeal are Client Apps using GPay, PhonePe, BharatPe as external services (UPI payments)
- Sharekhan, Upstox, Zerodha, Paisa, Angel One are Client Apps using BSE, NSE Comps as external services
- CrickBuzz, CricInfo, Dream11, ESPNsports are Client Apps using ICC Score Comps as external services
- Flipkart, Amazon, Myntra are Client Apps using PayPal, PayTM, Razorpay as external services (card payment)
2. The Problem: JNDI Lookup in BusinessDelegate
- No reusability: If multiple BusinessDelegate comps want to interact with the same external service, we need the same JNDI code in multiple BusinessDelegate classes. There is no reusability for JNDI lookup code.
- Location dependency: If the external comp location or technology is modified, then we need to modify JNDI lookup code in BusinessDelegate class.
- Registry dependency: If the JNDI registry which maintains the external comp reference location or technology is changed, then the JNDI lookup code in BusinessDelegate class will also be changed.
- Network overhead: Since we are not maintaining external comp ref in cache/buffer, there will be more network round trips between Client App and JNDI registry across multiple requests.
3. The Solution: Service Locator with Cache
Separate the JNDI lookup code from BusinessDelegate class and keep it in a separate class. This special class should maintain a buffer/cache having external comp references. This class is called Service Locator class.
It contains JNDI lookup code to locate and get external comp ref from JNDI registry. On first lookup, it fetches from registry and caches. On subsequent lookups, it returns from cache (no network call).
Advantages of Service Locator
- Reusable lookup code: Separates the JNDI lookup from BusinessDelegate classes. Makes the code reusable across multiple BusinessDelegates.
- Loose coupling with external comp: Any change in external comp location and technology, we just need to modify the code of Service Locator, not the BusinessDelegate classes.
- Loose coupling with registry: Any change in JNDI registry location and technology, we just need to modify the code of Service Locator, not the BusinessDelegate classes.
- Reduced network round trips: The buffer/cache maintained by Service Locator reduces the network round trips between client application and JNDI registry.
Since the ServiceLocator class is having buffer/cache as the sharable state across the multiple BusinessDelegate classes, it is better to develop ServiceLocator class as singleton Java class.
Service Locator as singleton Java class is the best use case for making a Java class as singleton when the class has sharable state across multiple other classes.
Note: BusinessDelegate contains the local business logic AND logic to invoke external comp service methods. ServiceLocator contains only lookup code to get external comp ref from JNDI registry. ServiceLocator is a helper comp to BusinessDelegate comp.
4. CrickBuzz Example: Consuming ICC Score Service
CrickBuzz.com (India) needs to display live cricket scores. The ICC (International Cricket Council) in Dubai maintains the score data. ICC develops one external service/distributed comp called ICCScoreComp and keeps its details in JNDI registry to expose to multiple clients like CrickBuzz, CricInfo, and ESPNsports. All client apps access the DB of ICC having score information through this service.
Different options for CrickBuzz.com to display updated scores:
- Send a person to match area, gets up to scores minute by minute by phone and enter in the website (lots of manual parties, not recommended)
- ICC organization provides its DB username, password details to CrickBuzz.com to connect to DB directly (gives unrestricted access to DB, can corrupt the DB data)
- Best: ICC develops one external service (ICCScoreComp) and keeps its details in JNDI registry to expose to multiple clients. All client apps access the DB of ICC having scores mentioned.
If the external comp (ICCScoreComp) is developed in EJB or RMI, then the client app (CrickBuzz, CricInfo) should also be developed in Java language. If the external comp is developed as WebServices, then the client app can be developed in any language (Java, .NET, PHP, Python, etc.).
5. Full-Length Industry Project: Stock Trading System (ShareKhan)
ShareKhan.com is a stock trading client application. It needs data from three external services hosted by BSE (Bombay Stock Exchange): (1) GetStockIdComp - looks up stock ID by name, (2) GetStockValueComp - gets current stock price by ID, (3) GetStockFutureValueComp - gets predicted future value. These three external services are registered in JNDI registry. A Session Facade on the server side aggregates all three calls into one. A Service Locator on the client side caches the Facade reference.
High-Level Design (HLD)
Low-Level Design (LLD)
Project Structure
ServiceLocator.java (Singleton with cache)
package com.sharekhan.locator;
import java.util.HashMap;
import java.util.Map;
public class ServiceLocator {
private static volatile ServiceLocator INSTANCE;
private final Map<String, Object> cache = new HashMap<>();
private ServiceLocator() {
System.out.println("[ServiceLocator] Initialized (singleton)");
}
public static ServiceLocator getInstance() {
if (INSTANCE == null) {
synchronized (ServiceLocator.class) {
if (INSTANCE == null) {
INSTANCE = new ServiceLocator();
}
}
}
return INSTANCE;
}
@SuppressWarnings("unchecked")
public <T> T getService(String jndiName, Class<T> serviceType) {
// Check cache first
if (cache.containsKey(jndiName)) {
System.out.println("[ServiceLocator] Cache HIT for: " + jndiName);
return (T) cache.get(jndiName);
}
// Cache miss: perform JNDI lookup (simulated)
System.out.println("[ServiceLocator] Cache MISS. Looking up: " + jndiName);
T service = performLookup(jndiName, serviceType);
// Cache the reference
cache.put(jndiName, service);
System.out.println("[ServiceLocator] Cached: " + jndiName);
return service;
}
@SuppressWarnings("unchecked")
private <T> T performLookup(String jndiName, Class<T> serviceType) {
// Simulated JNDI/registry lookup
// In real app: InitialContext ctx = new InitialContext();
// return (T) ctx.lookup(jndiName);
System.out.println("[ServiceLocator] JNDI lookup for: " + jndiName
+ " (network call to registry)");
// Simulate creating the service based on JNDI name
switch (jndiName) {
case "stockFacade":
return (T) new com.sharekhan.facade.StockFacadeImpl();
default:
throw new RuntimeException("Service not found in registry: " + jndiName);
}
}
public int getCacheSize() {
return cache.size();
}
}
External Services (simulated)
// IGetStockId.java
package com.sharekhan.external;
public interface IGetStockId {
int getStockId(String stockName);
}
// GetStockIdImpl.java
package com.sharekhan.external;
public class GetStockIdImpl implements IGetStockId {
public int getStockId(String stockName) {
System.out.println(" [External Service 1] Looking up ID for: " + stockName);
switch (stockName.toUpperCase()) {
case "RELIANCE": return 500325;
case "TCS": return 532540;
case "INFOSYS": return 500209;
case "HDFC": return 500180;
default: return -1;
}
}
}
// IGetStockValue.java
package com.sharekhan.external;
public interface IGetStockValue {
double getStockValue(int stockId);
}
// GetStockValueImpl.java
package com.sharekhan.external;
public class GetStockValueImpl implements IGetStockValue {
public double getStockValue(int stockId) {
System.out.println(" [External Service 2] Getting value for ID: " + stockId);
switch (stockId) {
case 500325: return 2485.50; // Reliance
case 532540: return 3678.25; // TCS
case 500209: return 1542.75; // Infosys
case 500180: return 1623.00; // HDFC
default: return 0.0;
}
}
}
// IGetStockFutureValue.java
package com.sharekhan.external;
public interface IGetStockFutureValue {
double getFutureValue(int stockId);
}
// GetStockFutureValueImpl.java
package com.sharekhan.external;
public class GetStockFutureValueImpl implements IGetStockFutureValue {
public double getFutureValue(int stockId) {
System.out.println(" [External Service 3] Predicting future for ID: " + stockId);
switch (stockId) {
case 500325: return 2650.00;
case 532540: return 3900.00;
case 500209: return 1680.00;
case 500180: return 1750.00;
default: return 0.0;
}
}
}
StockDetailsDTO.java
package com.sharekhan.dto;
public class StockDetailsDTO {
private final String stockName;
private final int stockId;
private final double currentValue;
private final double futureValue;
private final double changePercent;
public StockDetailsDTO(String stockName, int stockId,
double currentValue, double futureValue) {
this.stockName = stockName;
this.stockId = stockId;
this.currentValue = currentValue;
this.futureValue = futureValue;
this.changePercent = ((futureValue - currentValue) / currentValue) * 100;
}
public String getStockName() { return stockName; }
public int getStockId() { return stockId; }
public double getCurrentValue() { return currentValue; }
public double getFutureValue() { return futureValue; }
public double getChangePercent() { return changePercent; }
@Override
public String toString() {
return String.format("%s (ID: %d) | Current: Rs %.2f | Future: Rs %.2f | Change: %.1f%%",
stockName, stockId, currentValue, futureValue, changePercent);
}
}
IStockFacade.java and StockFacadeImpl.java (Session Facade)
// IStockFacade.java
package com.sharekhan.facade;
import com.sharekhan.dto.StockDetailsDTO;
public interface IStockFacade {
StockDetailsDTO getCompleteStockInfo(String stockName);
}
// StockFacadeImpl.java (aggregates 3 external services into ONE call)
package com.sharekhan.facade;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.external.*;
public class StockFacadeImpl implements IStockFacade {
private final IGetStockId stockIdService;
private final IGetStockValue stockValueService;
private final IGetStockFutureValue stockFutureService;
public StockFacadeImpl() {
this.stockIdService = new GetStockIdImpl();
this.stockValueService = new GetStockValueImpl();
this.stockFutureService = new GetStockFutureValueImpl();
System.out.println("[StockFacade] Initialized with 3 external services");
}
@Override
public StockDetailsDTO getCompleteStockInfo(String stockName) {
System.out.println("[StockFacade] Aggregating data for: " + stockName);
// Call 3 external services (server-side, no extra network calls)
int stockId = stockIdService.getStockId(stockName);
if (stockId == -1) {
throw new RuntimeException("Stock not found: " + stockName);
}
double currentValue = stockValueService.getStockValue(stockId);
double futureValue = stockFutureService.getFutureValue(stockId);
return new StockDetailsDTO(stockName, stockId, currentValue, futureValue);
}
}
StockMgmt.java (BusinessDelegate)
package com.sharekhan.delegate;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.facade.IStockFacade;
import com.sharekhan.locator.ServiceLocator;
public class StockMgmt {
public StockDetailsDTO getStockDetails(String stockName) {
// Get StockFacade ref from ServiceLocator (cached after first lookup)
IStockFacade facade = ServiceLocator.getInstance()
.getService("stockFacade", IStockFacade.class);
// Invoke external comp method through facade
return facade.getCompleteStockInfo(stockName);
}
}
Application.java
package com.sharekhan;
import com.sharekhan.delegate.StockMgmt;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.locator.ServiceLocator;
public class Application {
public static void main(String[] args) {
StockMgmt delegate = new StockMgmt();
// Scenario 1: First request (JNDI lookup + cache)
System.out.println("=== Scenario 1: First Request (Reliance) ===");
StockDetailsDTO reliance = delegate.getStockDetails("Reliance");
System.out.println("Result: " + reliance);
System.out.println();
// Scenario 2: Second request (cache hit, no JNDI lookup)
System.out.println("=== Scenario 2: Second Request (TCS) ===");
StockDetailsDTO tcs = delegate.getStockDetails("TCS");
System.out.println("Result: " + tcs);
System.out.println();
// Scenario 3: Third request (cache hit again)
System.out.println("=== Scenario 3: Third Request (Infosys) ===");
StockDetailsDTO infosys = delegate.getStockDetails("Infosys");
System.out.println("Result: " + infosys);
System.out.println();
// Scenario 4: Verify ServiceLocator is singleton with cache
System.out.println("=== Scenario 4: ServiceLocator Stats ===");
System.out.println("Cache size: " + ServiceLocator.getInstance().getCacheSize());
System.out.println("ServiceLocator hashCode: " + ServiceLocator.getInstance().hashCode());
System.out.println();
// Scenario 5: Invalid stock
System.out.println("=== Scenario 5: Invalid Stock ===");
try {
delegate.getStockDetails("UNKNOWN_STOCK");
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Expected Output
Key observations from the project
- JNDI lookup happens only once. Scenario 1 shows "Cache MISS" and performs the lookup. Scenarios 2 and 3 show "Cache HIT" with no network call to the registry.
- ServiceLocator is a Singleton. Same hashCode across all calls. Buffer/cache is shared state.
- Session Facade aggregates 3 external services into 1 call. The client makes one network call to the Facade. The Facade internally calls 3 services on the server side (no extra network hops from client).
- BusinessDelegate is clean. It calls
ServiceLocator.getInstance().getService()to get the Facade reference and then invokes the business method. No JNDI code, no caching logic. - Adding a new external service: Register it in JNDI, add a case to ServiceLocator's
performLookup(). No BusinessDelegate changes.
6. Service Locator Combined with Other Patterns
| Pattern | Role in this architecture |
|---|---|
| Service Locator | Looks up and caches external service references from JNDI registry |
| Session Facade | Aggregates multiple external service calls into one coarse-grained call |
| Business Delegate | Contains local business logic and uses ServiceLocator + Facade |
| Singleton | ServiceLocator is a singleton (shared cache across all delegates) |
| Flyweight | The cached service references are reused (flyweight concept) |
7. When to Use and When NOT to Use
When to use Service Locator
| Scenario | Why |
|---|---|
| Multiple BusinessDelegates share same external service | Reuse lookup code, share cached reference |
| JNDI/registry lookups are expensive | Cache avoids repeated network calls |
| External service location may change | Only ServiceLocator code changes |
| EJB or RMI based distributed services | JNDI lookup is the standard discovery mechanism |
When NOT to use (modern alternatives)
| Scenario | Alternative |
|---|---|
| Spring Boot application | Spring Dependency Injection replaces Service Locator. @Autowired injects services. |
| REST-based microservices | Service Discovery (Eureka, Consul) replaces JNDI. Ribbon/Feign handles lookup. |
| Simple application with few services | Direct injection or Factory pattern is simpler. |
Martin Fowler (2004) described Service Locator as an anti-pattern when Dependency Injection is available. In modern frameworks (Spring, CDI), DI is preferred because the container manages the lifecycle, and the class explicitly declares its dependencies. Service Locator hides dependencies, making them harder to test and understand.
However, Service Locator is still valid in: (a) legacy JEE applications, (b) environments without a DI container, (c) runtime service discovery scenarios.
8. Quick Reference Card
| Component | Role | In Stock Trading Project |
|---|---|---|
| Service Locator | Singleton with cache, performs JNDI lookup | ServiceLocator |
| Cache | HashMap of JNDI name to service reference | cache: Map<String, Object> |
| Business Delegate | Uses Service Locator to get service ref | StockMgmt |
| Session Facade | Aggregates multiple services into one call | StockFacadeImpl |
| External Services | Distributed components in registry | GetStockIdImpl, GetStockValueImpl, etc. |
Service Locator Design Pattern in Java
1. What is Service Locator Pattern?
The Service Locator pattern is a JEE Business Tier Pattern. It separates JNDI/registry lookup code from the BusinessDelegate class and keeps it in a dedicated class that maintains a buffer/cache of external service component references. The Service Locator contains JNDI lookup code to locate and get external comp references from the registry.
One line: A centralized registry lookup class that caches external service references to avoid repeated costly JNDI lookups across multiple requests.
Local Services vs External Services
| Type | Description | Examples |
|---|---|---|
| Local Services | Service logics used only inside the project. BusinessDelegate class logics are local services. | Flipkart's billing calculations, discount logic, cart operations |
| External Services (Distributed Comps) | Service logics that can be accessed and used from different projects. Accessed over the network using distributed technologies. | GPay, PhonePe, BharatPe, ICC Score Comp, BSE Comp, WeatherReport comp |
How external services are discovered
To make our logics/services as external services or distributed comps, we need to take the support of Distributed Technologies like EJB (old), RMI (old), WebServices (SOAP or REST based).
The distributed comps/external comps keep their comp details in special registries to expose the details to different local or remote client projects. The special registries are like:
- JNDI Registry (Java Naming and Directory Interface)
- RMI Registry
- UDDI Registry (for SOAP WebServices)
The remote or local Client Apps/Projects search for external services/distributed comp in JNDI registry or UDDI registry and get references from those registries in order to consume the services of external service or distributed comp.
Real-world examples of client-service relationships
- Flipkart, Amazon, Myntra, Snapdeal are Client Apps using GPay, PhonePe, BharatPe as external services (UPI payments)
- Sharekhan, Upstox, Zerodha, Paisa, Angel One are Client Apps using BSE, NSE Comps as external services
- CrickBuzz, CricInfo, Dream11, ESPNsports are Client Apps using ICC Score Comps as external services
- Flipkart, Amazon, Myntra are Client Apps using PayPal, PayTM, Razorpay as external services (card payment)
2. The Problem: JNDI Lookup in BusinessDelegate
- No reusability: If multiple BusinessDelegate comps want to interact with the same external service, we need the same JNDI code in multiple BusinessDelegate classes. There is no reusability for JNDI lookup code.
- Location dependency: If the external comp location or technology is modified, then we need to modify JNDI lookup code in BusinessDelegate class.
- Registry dependency: If the JNDI registry which maintains the external comp reference location or technology is changed, then the JNDI lookup code in BusinessDelegate class will also be changed.
- Network overhead: Since we are not maintaining external comp ref in cache/buffer, there will be more network round trips between Client App and JNDI registry across multiple requests.
3. The Solution: Service Locator with Cache
Separate the JNDI lookup code from BusinessDelegate class and keep it in a separate class. This special class should maintain a buffer/cache having external comp references. This class is called Service Locator class.
It contains JNDI lookup code to locate and get external comp ref from JNDI registry. On first lookup, it fetches from registry and caches. On subsequent lookups, it returns from cache (no network call).
Advantages of Service Locator
- Reusable lookup code: Separates the JNDI lookup from BusinessDelegate classes. Makes the code reusable across multiple BusinessDelegates.
- Loose coupling with external comp: Any change in external comp location and technology, we just need to modify the code of Service Locator, not the BusinessDelegate classes.
- Loose coupling with registry: Any change in JNDI registry location and technology, we just need to modify the code of Service Locator, not the BusinessDelegate classes.
- Reduced network round trips: The buffer/cache maintained by Service Locator reduces the network round trips between client application and JNDI registry.
Since the ServiceLocator class is having buffer/cache as the sharable state across the multiple BusinessDelegate classes, it is better to develop ServiceLocator class as singleton Java class.
Service Locator as singleton Java class is the best use case for making a Java class as singleton when the class has sharable state across multiple other classes.
Note: BusinessDelegate contains the local business logic AND logic to invoke external comp service methods. ServiceLocator contains only lookup code to get external comp ref from JNDI registry. ServiceLocator is a helper comp to BusinessDelegate comp.
4. CrickBuzz Example: Consuming ICC Score Service
CrickBuzz.com (India) needs to display live cricket scores. The ICC (International Cricket Council) in Dubai maintains the score data. ICC develops one external service/distributed comp called ICCScoreComp and keeps its details in JNDI registry to expose to multiple clients like CrickBuzz, CricInfo, and ESPNsports. All client apps access the DB of ICC having score information through this service.
Different options for CrickBuzz.com to display updated scores:
- Send a person to match area, gets up to scores minute by minute by phone and enter in the website (lots of manual parties, not recommended)
- ICC organization provides its DB username, password details to CrickBuzz.com to connect to DB directly (gives unrestricted access to DB, can corrupt the DB data)
- Best: ICC develops one external service (ICCScoreComp) and keeps its details in JNDI registry to expose to multiple clients. All client apps access the DB of ICC having scores mentioned.
If the external comp (ICCScoreComp) is developed in EJB or RMI, then the client app (CrickBuzz, CricInfo) should also be developed in Java language. If the external comp is developed as WebServices, then the client app can be developed in any language (Java, .NET, PHP, Python, etc.).
5. Full-Length Industry Project: Stock Trading System (ShareKhan)
ShareKhan.com is a stock trading client application. It needs data from three external services hosted by BSE (Bombay Stock Exchange): (1) GetStockIdComp - looks up stock ID by name, (2) GetStockValueComp - gets current stock price by ID, (3) GetStockFutureValueComp - gets predicted future value. These three external services are registered in JNDI registry. A Session Facade on the server side aggregates all three calls into one. A Service Locator on the client side caches the Facade reference.
High-Level Design (HLD)
Low-Level Design (LLD)
Project Structure
ServiceLocator.java (Singleton with cache)
package com.sharekhan.locator;
import java.util.HashMap;
import java.util.Map;
public class ServiceLocator {
private static volatile ServiceLocator INSTANCE;
private final Map<String, Object> cache = new HashMap<>();
private ServiceLocator() {
System.out.println("[ServiceLocator] Initialized (singleton)");
}
public static ServiceLocator getInstance() {
if (INSTANCE == null) {
synchronized (ServiceLocator.class) {
if (INSTANCE == null) {
INSTANCE = new ServiceLocator();
}
}
}
return INSTANCE;
}
@SuppressWarnings("unchecked")
public <T> T getService(String jndiName, Class<T> serviceType) {
// Check cache first
if (cache.containsKey(jndiName)) {
System.out.println("[ServiceLocator] Cache HIT for: " + jndiName);
return (T) cache.get(jndiName);
}
// Cache miss: perform JNDI lookup (simulated)
System.out.println("[ServiceLocator] Cache MISS. Looking up: " + jndiName);
T service = performLookup(jndiName, serviceType);
// Cache the reference
cache.put(jndiName, service);
System.out.println("[ServiceLocator] Cached: " + jndiName);
return service;
}
@SuppressWarnings("unchecked")
private <T> T performLookup(String jndiName, Class<T> serviceType) {
// Simulated JNDI/registry lookup
// In real app: InitialContext ctx = new InitialContext();
// return (T) ctx.lookup(jndiName);
System.out.println("[ServiceLocator] JNDI lookup for: " + jndiName
+ " (network call to registry)");
// Simulate creating the service based on JNDI name
switch (jndiName) {
case "stockFacade":
return (T) new com.sharekhan.facade.StockFacadeImpl();
default:
throw new RuntimeException("Service not found in registry: " + jndiName);
}
}
public int getCacheSize() {
return cache.size();
}
}
External Services (simulated)
// IGetStockId.java
package com.sharekhan.external;
public interface IGetStockId {
int getStockId(String stockName);
}
// GetStockIdImpl.java
package com.sharekhan.external;
public class GetStockIdImpl implements IGetStockId {
public int getStockId(String stockName) {
System.out.println(" [External Service 1] Looking up ID for: " + stockName);
switch (stockName.toUpperCase()) {
case "RELIANCE": return 500325;
case "TCS": return 532540;
case "INFOSYS": return 500209;
case "HDFC": return 500180;
default: return -1;
}
}
}
// IGetStockValue.java
package com.sharekhan.external;
public interface IGetStockValue {
double getStockValue(int stockId);
}
// GetStockValueImpl.java
package com.sharekhan.external;
public class GetStockValueImpl implements IGetStockValue {
public double getStockValue(int stockId) {
System.out.println(" [External Service 2] Getting value for ID: " + stockId);
switch (stockId) {
case 500325: return 2485.50; // Reliance
case 532540: return 3678.25; // TCS
case 500209: return 1542.75; // Infosys
case 500180: return 1623.00; // HDFC
default: return 0.0;
}
}
}
// IGetStockFutureValue.java
package com.sharekhan.external;
public interface IGetStockFutureValue {
double getFutureValue(int stockId);
}
// GetStockFutureValueImpl.java
package com.sharekhan.external;
public class GetStockFutureValueImpl implements IGetStockFutureValue {
public double getFutureValue(int stockId) {
System.out.println(" [External Service 3] Predicting future for ID: " + stockId);
switch (stockId) {
case 500325: return 2650.00;
case 532540: return 3900.00;
case 500209: return 1680.00;
case 500180: return 1750.00;
default: return 0.0;
}
}
}
StockDetailsDTO.java
package com.sharekhan.dto;
public class StockDetailsDTO {
private final String stockName;
private final int stockId;
private final double currentValue;
private final double futureValue;
private final double changePercent;
public StockDetailsDTO(String stockName, int stockId,
double currentValue, double futureValue) {
this.stockName = stockName;
this.stockId = stockId;
this.currentValue = currentValue;
this.futureValue = futureValue;
this.changePercent = ((futureValue - currentValue) / currentValue) * 100;
}
public String getStockName() { return stockName; }
public int getStockId() { return stockId; }
public double getCurrentValue() { return currentValue; }
public double getFutureValue() { return futureValue; }
public double getChangePercent() { return changePercent; }
@Override
public String toString() {
return String.format("%s (ID: %d) | Current: Rs %.2f | Future: Rs %.2f | Change: %.1f%%",
stockName, stockId, currentValue, futureValue, changePercent);
}
}
IStockFacade.java and StockFacadeImpl.java (Session Facade)
// IStockFacade.java
package com.sharekhan.facade;
import com.sharekhan.dto.StockDetailsDTO;
public interface IStockFacade {
StockDetailsDTO getCompleteStockInfo(String stockName);
}
// StockFacadeImpl.java (aggregates 3 external services into ONE call)
package com.sharekhan.facade;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.external.*;
public class StockFacadeImpl implements IStockFacade {
private final IGetStockId stockIdService;
private final IGetStockValue stockValueService;
private final IGetStockFutureValue stockFutureService;
public StockFacadeImpl() {
this.stockIdService = new GetStockIdImpl();
this.stockValueService = new GetStockValueImpl();
this.stockFutureService = new GetStockFutureValueImpl();
System.out.println("[StockFacade] Initialized with 3 external services");
}
@Override
public StockDetailsDTO getCompleteStockInfo(String stockName) {
System.out.println("[StockFacade] Aggregating data for: " + stockName);
// Call 3 external services (server-side, no extra network calls)
int stockId = stockIdService.getStockId(stockName);
if (stockId == -1) {
throw new RuntimeException("Stock not found: " + stockName);
}
double currentValue = stockValueService.getStockValue(stockId);
double futureValue = stockFutureService.getFutureValue(stockId);
return new StockDetailsDTO(stockName, stockId, currentValue, futureValue);
}
}
StockMgmt.java (BusinessDelegate)
package com.sharekhan.delegate;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.facade.IStockFacade;
import com.sharekhan.locator.ServiceLocator;
public class StockMgmt {
public StockDetailsDTO getStockDetails(String stockName) {
// Get StockFacade ref from ServiceLocator (cached after first lookup)
IStockFacade facade = ServiceLocator.getInstance()
.getService("stockFacade", IStockFacade.class);
// Invoke external comp method through facade
return facade.getCompleteStockInfo(stockName);
}
}
Application.java
package com.sharekhan;
import com.sharekhan.delegate.StockMgmt;
import com.sharekhan.dto.StockDetailsDTO;
import com.sharekhan.locator.ServiceLocator;
public class Application {
public static void main(String[] args) {
StockMgmt delegate = new StockMgmt();
// Scenario 1: First request (JNDI lookup + cache)
System.out.println("=== Scenario 1: First Request (Reliance) ===");
StockDetailsDTO reliance = delegate.getStockDetails("Reliance");
System.out.println("Result: " + reliance);
System.out.println();
// Scenario 2: Second request (cache hit, no JNDI lookup)
System.out.println("=== Scenario 2: Second Request (TCS) ===");
StockDetailsDTO tcs = delegate.getStockDetails("TCS");
System.out.println("Result: " + tcs);
System.out.println();
// Scenario 3: Third request (cache hit again)
System.out.println("=== Scenario 3: Third Request (Infosys) ===");
StockDetailsDTO infosys = delegate.getStockDetails("Infosys");
System.out.println("Result: " + infosys);
System.out.println();
// Scenario 4: Verify ServiceLocator is singleton with cache
System.out.println("=== Scenario 4: ServiceLocator Stats ===");
System.out.println("Cache size: " + ServiceLocator.getInstance().getCacheSize());
System.out.println("ServiceLocator hashCode: " + ServiceLocator.getInstance().hashCode());
System.out.println();
// Scenario 5: Invalid stock
System.out.println("=== Scenario 5: Invalid Stock ===");
try {
delegate.getStockDetails("UNKNOWN_STOCK");
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
Expected Output
Key observations from the project
- JNDI lookup happens only once. Scenario 1 shows "Cache MISS" and performs the lookup. Scenarios 2 and 3 show "Cache HIT" with no network call to the registry.
- ServiceLocator is a Singleton. Same hashCode across all calls. Buffer/cache is shared state.
- Session Facade aggregates 3 external services into 1 call. The client makes one network call to the Facade. The Facade internally calls 3 services on the server side (no extra network hops from client).
- BusinessDelegate is clean. It calls
ServiceLocator.getInstance().getService()to get the Facade reference and then invokes the business method. No JNDI code, no caching logic. - Adding a new external service: Register it in JNDI, add a case to ServiceLocator's
performLookup(). No BusinessDelegate changes.
6. Service Locator Combined with Other Patterns
| Pattern | Role in this architecture |
|---|---|
| Service Locator | Looks up and caches external service references from JNDI registry |
| Session Facade | Aggregates multiple external service calls into one coarse-grained call |
| Business Delegate | Contains local business logic and uses ServiceLocator + Facade |
| Singleton | ServiceLocator is a singleton (shared cache across all delegates) |
| Flyweight | The cached service references are reused (flyweight concept) |
7. When to Use and When NOT to Use
When to use Service Locator
| Scenario | Why |
|---|---|
| Multiple BusinessDelegates share same external service | Reuse lookup code, share cached reference |
| JNDI/registry lookups are expensive | Cache avoids repeated network calls |
| External service location may change | Only ServiceLocator code changes |
| EJB or RMI based distributed services | JNDI lookup is the standard discovery mechanism |
When NOT to use (modern alternatives)
| Scenario | Alternative |
|---|---|
| Spring Boot application | Spring Dependency Injection replaces Service Locator. @Autowired injects services. |
| REST-based microservices | Service Discovery (Eureka, Consul) replaces JNDI. Ribbon/Feign handles lookup. |
| Simple application with few services | Direct injection or Factory pattern is simpler. |
Martin Fowler (2004) described Service Locator as an anti-pattern when Dependency Injection is available. In modern frameworks (Spring, CDI), DI is preferred because the container manages the lifecycle, and the class explicitly declares its dependencies. Service Locator hides dependencies, making them harder to test and understand.
However, Service Locator is still valid in: (a) legacy JEE applications, (b) environments without a DI container, (c) runtime service discovery scenarios.
8. Quick Reference Card
| Component | Role | In Stock Trading Project |
|---|---|---|
| Service Locator | Singleton with cache, performs JNDI lookup | ServiceLocator |
| Cache | HashMap of JNDI name to service reference | cache: Map<String, Object> |
| Business Delegate | Uses Service Locator to get service ref | StockMgmt |
| Session Facade | Aggregates multiple services into one call | StockFacadeImpl |
| External Services | Distributed components in registry | GetStockIdImpl, GetStockValueImpl, etc. |