Modules with Circular Dependencies

Description

Modules with Circular Dependencies occurs when software contains modules where one module has references that cycle back to itself through other modules. This creates a situation where module A depends on module B, which depends on module C, which depends back on module A (or simpler: A depends on B, B depends on A). In Java, this often manifests as cycles between packages. Circular dependencies indicate poor architectural design and tight coupling between components that should be independent.

Risk

While primarily an architectural quality issue, circular dependencies have indirect security implications. They make the codebase harder to understand, maintain, and audit for security vulnerabilities. Security patches become riskier because changes in one module may unexpectedly affect circularly dependent modules. Testing becomes more complex, potentially leaving security issues undiscovered. The tight coupling makes it difficult to isolate and secure individual components. Build and deployment processes become more fragile, potentially leading to incomplete security updates.

Solution

Refactor code to break circular dependencies through several strategies: (1) Extract shared code into a separate module that both dependent modules can reference, (2) Use dependency injection to invert dependencies, (3) Define interfaces in a separate module to break direct dependencies, (4) Apply the Dependency Inversion Principle (DIP) to depend on abstractions rather than concrete implementations, (5) Use event-driven or message-based communication instead of direct calls. Employ static analysis tools and build systems that detect circular dependencies. Establish architectural guidelines that prohibit cycles.

Common Consequences

ImpactDetails
OtherScope: Other

Reduce Maintainability - Circular dependencies create tight coupling that makes the codebase harder to maintain, test, and secure.
OtherScope: Other

Quality Degradation - Poor modularity undermines reliability and can mask security vulnerabilities during code review.

Example Code

Vulnerable Code

// Vulnerable: Circular dependency between modules
// Module A: com.example.orders
package com.example.orders;

import com.example.customers.CustomerService;  // Depends on customers module

public class OrderService {
    private CustomerService customerService;

    public OrderService(CustomerService customerService) {
        this.customerService = customerService;
    }

    public Order createOrder(String customerId, List<Item> items) {
        Customer customer = customerService.getCustomer(customerId);
        Order order = new Order(customer, items);
        // Notify customer service about the new order
        customerService.notifyNewOrder(customer, order);  // Circular!
        return order;
    }

    public List<Order> getOrdersByCustomer(String customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
}
// Module B: com.example.customers
package com.example.customers;

import com.example.orders.OrderService;  // Depends on orders module - CIRCULAR!

public class CustomerService {
    private OrderService orderService;  // Circular dependency!

    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }

    public Customer getCustomer(String id) {
        Customer customer = customerRepository.findById(id);
        // Get order history - creates circular dependency
        customer.setOrderHistory(orderService.getOrdersByCustomer(id));
        return customer;
    }

    public void notifyNewOrder(Customer customer, Order order) {
        // Send notification
    }

    public CustomerStats getCustomerStats(String customerId) {
        // Uses order service - circular dependency
        List<Order> orders = orderService.getOrdersByCustomer(customerId);
        return calculateStats(orders);
    }
}

// Problems:
// 1. Cannot compile/deploy modules independently
// 2. Cannot test modules in isolation
// 3. Changes ripple unpredictably between modules
// 4. Security patches to one module may break the other
# Vulnerable: Circular imports in Python
# file: services/user_service.py
from services.order_service import OrderService  # Circular!

class UserService:
    def __init__(self):
        self.order_service = OrderService()

    def get_user_with_orders(self, user_id):
        user = self.user_repo.find(user_id)
        user.orders = self.order_service.get_orders_for_user(user_id)
        return user
# file: services/order_service.py
from services.user_service import UserService  # Circular import!

class OrderService:
    def __init__(self):
        self.user_service = UserService()

    def get_orders_for_user(self, user_id):
        return self.order_repo.find_by_user(user_id)

    def create_order(self, user_id, items):
        user = self.user_service.get_user(user_id)
        # ... create order
// Vulnerable: Circular dependencies in TypeScript/JavaScript modules
// file: moduleA.ts
import { ClassB } from './moduleB';  // Depends on B

export class ClassA {
    private b: ClassB;

    constructor() {
        this.b = new ClassB();
    }

    methodA() {
        return this.b.methodB();
    }

    helperForB() {
        return "data from A";
    }
}
// file: moduleB.ts
import { ClassA } from './moduleA';  // Depends on A - CIRCULAR!

export class ClassB {
    private a: ClassA;

    constructor() {
        this.a = new ClassA();  // Infinite loop / undefined!
    }

    methodB() {
        return this.a.helperForB();
    }
}

Fixed Code

// Fixed: Breaking circular dependency with interface extraction

// Shared interfaces module: com.example.common
package com.example.common;

public interface OrderProvider {
    List<Order> getOrdersByCustomer(String customerId);
}

public interface CustomerProvider {
    Customer getCustomer(String id);
}

public interface OrderNotification {
    void notifyNewOrder(Customer customer, Order order);
}
// Fixed: Orders module depends only on interfaces
package com.example.orders;

import com.example.common.CustomerProvider;
import com.example.common.OrderNotification;

public class OrderService implements OrderProvider {
    private CustomerProvider customerProvider;
    private OrderNotification orderNotification;

    public OrderService(CustomerProvider customerProvider,
                        OrderNotification orderNotification) {
        this.customerProvider = customerProvider;
        this.orderNotification = orderNotification;
    }

    public Order createOrder(String customerId, List<Item> items) {
        Customer customer = customerProvider.getCustomer(customerId);
        Order order = new Order(customer, items);
        orderNotification.notifyNewOrder(customer, order);
        return order;
    }

    @Override
    public List<Order> getOrdersByCustomer(String customerId) {
        return orderRepository.findByCustomerId(customerId);
    }
}
// Fixed: Customers module depends on interface, implements notification
package com.example.customers;

import com.example.common.OrderProvider;
import com.example.common.OrderNotification;
import com.example.common.CustomerProvider;

public class CustomerService implements CustomerProvider, OrderNotification {
    private OrderProvider orderProvider;  // Interface, not concrete class

    // Setter injection - orderProvider set after construction
    public void setOrderProvider(OrderProvider orderProvider) {
        this.orderProvider = orderProvider;
    }

    @Override
    public Customer getCustomer(String id) {
        return customerRepository.findById(id);
    }

    // Order history retrieved separately, not during getCustomer
    public Customer getCustomerWithOrders(String id) {
        Customer customer = getCustomer(id);
        if (orderProvider != null) {
            customer.setOrderHistory(orderProvider.getOrdersByCustomer(id));
        }
        return customer;
    }

    @Override
    public void notifyNewOrder(Customer customer, Order order) {
        notificationService.send(customer, "New order: " + order.getId());
    }
}
// Fixed: Wiring with dependency injection (Spring example)
@Configuration
public class ServiceConfig {

    @Bean
    public OrderProvider orderService(CustomerProvider customerProvider,
                                       OrderNotification orderNotification) {
        return new OrderService(customerProvider, orderNotification);
    }

    @Bean
    public CustomerService customerService() {
        return new CustomerService();
    }

    @Bean
    public CustomerProvider customerProvider(CustomerService customerService,
                                             OrderProvider orderProvider) {
        customerService.setOrderProvider(orderProvider);
        return customerService;
    }
}
# Fixed: Breaking circular imports with dependency injection
# file: services/interfaces.py
from abc import ABC, abstractmethod

class UserProviderInterface(ABC):
    @abstractmethod
    def get_user(self, user_id: str): pass

class OrderProviderInterface(ABC):
    @abstractmethod
    def get_orders_for_user(self, user_id: str): pass
# file: services/user_service.py
from services.interfaces import UserProviderInterface, OrderProviderInterface

class UserService(UserProviderInterface):
    def __init__(self):
        self._order_provider = None

    def set_order_provider(self, order_provider: OrderProviderInterface):
        self._order_provider = order_provider

    def get_user(self, user_id: str):
        return self.user_repo.find(user_id)

    def get_user_with_orders(self, user_id: str):
        user = self.get_user(user_id)
        if self._order_provider:
            user.orders = self._order_provider.get_orders_for_user(user_id)
        return user
# file: services/order_service.py
from services.interfaces import UserProviderInterface, OrderProviderInterface

class OrderService(OrderProviderInterface):
    def __init__(self, user_provider: UserProviderInterface):
        self.user_provider = user_provider

    def get_orders_for_user(self, user_id: str):
        return self.order_repo.find_by_user(user_id)

    def create_order(self, user_id: str, items):
        user = self.user_provider.get_user(user_id)
        # ... create order
# file: services/container.py - Wiring
from services.user_service import UserService
from services.order_service import OrderService

def create_services():
    user_service = UserService()
    order_service = OrderService(user_service)
    user_service.set_order_provider(order_service)
    return user_service, order_service
// Fixed: Event-based communication to avoid circular dependencies
// file: events/eventBus.ts
type EventHandler = (data: any) => void;

class EventBus {
    private handlers: Map<string, EventHandler[]> = new Map();

    subscribe(event: string, handler: EventHandler) {
        if (!this.handlers.has(event)) {
            this.handlers.set(event, []);
        }
        this.handlers.get(event)!.push(handler);
    }

    publish(event: string, data: any) {
        const handlers = this.handlers.get(event) || [];
        handlers.forEach(handler => handler(data));
    }
}

export const eventBus = new EventBus();
// file: moduleA.ts - No direct import of moduleB
import { eventBus } from './events/eventBus';

export class ClassA {
    constructor() {
        // Listen for events from B
        eventBus.subscribe('b:dataReady', (data) => {
            this.handleBData(data);
        });
    }

    methodA() {
        // Request data from B via event
        eventBus.publish('a:needData', { requestId: '123' });
    }

    private handleBData(data: any) {
        // Process data from B
    }
}
// file: moduleB.ts - No direct import of moduleA
import { eventBus } from './events/eventBus';

export class ClassB {
    constructor() {
        // Listen for requests from A
        eventBus.subscribe('a:needData', (request) => {
            const data = this.computeData();
            eventBus.publish('b:dataReady', data);
        });
    }

    private computeData() {
        return "data from B";
    }
}

CVE Examples

This CWE is marked as PROHIBITED for direct CVE mapping as it represents an architectural quality concern rather than a direct security vulnerability.


  • CWE-1120: Excessive Code Complexity (parent)
  • CWE-1226: Complexity Issues (category member)
  • CWE-710: Improper Adherence to Coding Standards (related)

References

  1. MITRE Corporation. "CWE-1047: Modules with Circular Dependencies." https://cwe.mitre.org/data/definitions/1047.html
  2. Martin, Robert C. "Clean Architecture."
  3. Fowler, Martin. "Patterns of Enterprise Application Architecture."