Module mit zirkularen Abhängigkeiten
Beschreibung
Module mit zirkularen Abhängigkeiten tritt auf, wenn Software Module enthält, bei denen ein Modul Referenzen hat, die über andere Module auf sich selbst zurückverweisen. Dies erzeugt eine Situation, in der Modul A von Modul B abhängt, welches von Modul C abhängt, welches wiederum von Modul A abhängt (oder einfacher: A hängt von B ab, B hängt von A ab). In Java manifestiert sich dies oft als Zyklen zwischen Packages. Zirkulare Abhängigkeiten deuten auf schlechtes architektonisches Design und enge Kopplung zwischen Komponenten hin, die unabhängig sein sollten.
Risiko
Obwohl hauptsächlich ein architektonisches Qualitätsproblem, haben zirkulare Abhängigkeiten indirekte Sicherheitsimplikationen. Sie machen die Codebasis schwerer zu verstehen, zu warten und auf Sicherheitsschwachstellen zu überprüfen. Sicherheitspatches werden riskanter, weil Änderungen in einem Modul unerwartet zirkular abhängige Module beeinflussen können. Das Testen wird komplexer, was möglicherweise Sicherheitsprobleme unentdeckt lässt. Die enge Kopplung macht es schwierig, einzelne Komponenten zu isolieren und zu sichern.
Lösung
Refaktorisieren Sie Code, um zirkulare Abhängigkeiten durch mehrere Strategien zu brechen: (1) Extrahieren Sie gemeinsamen Code in ein separates Modul, auf das beide abhängigen Module verweisen können, (2) Verwenden Sie Dependency Injection zur Umkehrung von Abhängigkeiten, (3) Definieren Sie Interfaces in einem separaten Modul, um direkte Abhängigkeiten zu brechen, (4) Wenden Sie das Dependency Inversion Principle (DIP) an, um von Abstraktionen statt konkreten Implementierungen abzuhängen, (5) Verwenden Sie ereignisgesteuerte oder nachrichtenbasierte Kommunikation anstelle direkter Aufrufe.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Wartbarkeit - Zirkulare Abhängigkeiten erzeugen enge Kopplung, die die Codebasis schwerer zu warten, zu testen und zu sichern macht. |
| Andere | Bereich: Ändere Qualitätsverschlechterung - Schlechte Modularität untergräbt die Zuverlässigkeit und kann Sicherheitsschwachstellen bei Code-Reviews maskieren. |
Beispielcode
Anfälliger Code
// ANFÄLLIG: Zirkulare Abhängigkeit zwischen Modulen
// Modul A: com.example.orders
package com.example.orders;
import com.example.customers.CustomerService; // Hängt von customers-Modul ab
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);
// Benachrichtigt Customer-Service über neue Bestellung
customerService.notifyNewOrder(customer, order); // Zirkular!
return order;
}
public List<Order> getOrdersByCustomer(String customerId) {
return orderRepository.findByCustomerId(customerId);
}
}
// Modul B: com.example.customers
package com.example.customers;
import com.example.orders.OrderService; // Hängt von orders-Modul ab - ZIRKULAR!
public class CustomerService {
private OrderService orderService; // Zirkulare Abhängigkeit!
public void setOrderService(OrderService orderService) {
this.orderService = orderService;
}
public Customer getCustomer(String id) {
Customer customer = customerRepository.findById(id);
// Bestellhistorie abrufen - erzeugt zirkulare Abhängigkeit
customer.setOrderHistory(orderService.getOrdersByCustomer(id));
return customer;
}
public void notifyNewOrder(Customer customer, Order order) {
// Benachrichtigung senden
}
}
// Probleme:
// 1. Module können nicht unabhängig kompiliert/bereitgestellt werden
// 2. Module können nicht isoliert getestet werden
// 3. Änderungen breiten sich unvorhersehbar zwischen Modulen aus
// 4. Sicherheitspatches zu einem Modul können das andere brechen
# ANFÄLLIG: Zirkulare Imports in Python
# Datei: services/user_service.py
from services.order_service import OrderService # Zirkular!
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
# Datei: services/order_service.py
from services.user_service import UserService # Zirkularer 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)
# ... Bestellung erstellen
Korrigierter Code
// KORRIGIERT: Zirkulare Abhängigkeit mit Interface-Extraktion brechen
// Gemeinsames Interfaces-Modul: 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);
}
// KORRIGIERT: Orders-Modul hängt nur von Interfaces ab
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);
}
}
// KORRIGIERT: Customers-Modul hängt von Interface ab, implementiert 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, nicht konkrete Klasse
// Setter-Injection - orderProvider nach Konstruktion gesetzt
public void setOrderProvider(OrderProvider orderProvider) {
this.orderProvider = orderProvider;
}
@Override
public Customer getCustomer(String id) {
return customerRepository.findById(id);
}
// Bestellhistorie separat abgerufen, nicht während 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, "Neue Bestellung: " + order.getId());
}
}
# KORRIGIERT: Zirkulare Imports mit Dependency Injection brechen
# Datei: 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
# Datei: 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
# Datei: services/container.py - Verdrahtung
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
CVE-Beispiele
Diese CWE ist für direkte CVE-Zuordnung als VERBOTEN markiert, da sie ein architektonisches Qualitätsproblem und keine direkte Sicherheitsschwachstelle darstellt.
Verwandte CWEs
- CWE-1120: Excessive Code Complexity (Eltern)
- CWE-1226: Complexity Issues (Kategoriemitglied)
- CWE-710: Improper Adherence to Coding Standards (verwandt)
Referenzen
-
MITRE Corporation. "CWE-1047: Modules with Circular Dependencies." https://cwe.mitre.org/data/definitions/1047.html
-
Martin, Robert C. "Clean Architecture."
-
Fowler, Martin. "Patterns of Enterprise Application Architecture."