Layered Architecture:
Hunt the Wumpus Example:
Cross-Layer Communication:
#include
#include
// ------------------------------
// Layer 1: Data Access (Low-Level)
// ------------------------------
class IDataStorage {
public:
virtual std::string fetchData() = 0;
virtual ~IDataStorage() = default;
};
class Database : public IDataStorage {
public:
std::string fetchData() override {
return "Data from database";
}
};
// ------------------------------
// Layer 2: Business Logic (High-Level)
// ------------------------------
class BusinessService {
private:
std::shared_ptr<IDataStorage> storage;
public:
explicit BusinessService(std::shared_ptr<IDataStorage> storage) : storage(storage) {}
void process() {
std::string data = storage->fetchData();
std::cout << "Processing: " << data << std::endl;
}
};
// ------------------------------
// Layer 3: UI (Highest-Level)
// ------------------------------
class UIManager {
private:
std::shared_ptr<BusinessService> service;
public:
explicit UIManager(std::shared_ptr<BusinessService> service) : service(service) {}
void display() {
service->process();
}
};
// ------------------------------
// Main: Dependency Injection
// ------------------------------
int main() {
auto storage = std::make_shared<Database>();
auto service = std::make_shared<BusinessService>(storage);
UIManager ui(service);
ui.display(); // Output: "Processing: Data from database"
return 0;
}
Explanation:
BusinessService
depends on IDataStorage
(abstract), not Database
(concrete).Database
with a mock in tests.Main Component as Plugin:
Dependency Injection:
Database
instance and injecting it into BusinessService
).Framework Independence:
#include
#include
// ------------------------------
// Core Logic (Independent of Framework)
// ------------------------------
class IRepository {
public:
virtual std::string getMessage() = 0;
virtual ~IRepository() = default;
};
class CoreService {
private:
std::shared_ptr<IRepository> repo;
public:
explicit CoreService(std::shared_ptr<IRepository> repo) : repo(repo) {}
void run() {
std::cout << "Core: " << repo->getMessage() << std::endl;
}
};
// ------------------------------
// Framework-Specific Adapter (Plugin)
// ------------------------------
class FileRepository : public IRepository {
public:
std::string getMessage() override {
return "Message from file";
}
};
// ------------------------------
// Main Component (Initializes Dependencies)
// ------------------------------
int main() {
auto repo = std::make_shared<FileRepository>();
CoreService service(repo);
service.run(); // Output: "Core: Message from file"
return 0;
}
Explanation:
CoreService
depends on IRepository
, not FileRepository
.IRepository
in tests.DatabaseRepository
without changing CoreService
.Chapter 25:
BusinessService
depends on abstract IDataStorage
.Chapter 26:
CoreService
uses IRepository
injected by main()
.Focus: Cross-layer communication strategies, boundary enforcement, data flow management.
Focus: Bootstrapping dependencies, dependency inversion, minimalism in system initialization.
Question 1: Layered Architecture Boundaries
Which of the following are valid strategies for enforcing layer boundaries in Clean Architecture?
A. Using compiler-enforced package visibility rules.
B. Declaring all layers as public modules with unrestricted access.
C. Implementing dependency injection containers to isolate layer dependencies.
D. Relying on documentation to guide developers on layer interactions.
Question 2: Main Component Responsibilities
Which responsibilities belong to the Main component? (Choose two)
A. Business rule validation.
B. Dependency graph initialization.
C. HTTP request/response handling.
D. Configuration loading for infrastructure.
Question 3: Dependency Inversion in Layers
Which patterns help implement the Dependency Inversion Principle between layers? (Choose two)
A. Using abstract factories for service instantiation.
B. Directly instantiating concrete classes in higher layers.
C. Defining interface contracts in the domain layer.
D. Embedding database schemas in UI components.
Question 4: Boundary Violations
What issues arise from improper layer boundary enforcement? (Choose two)
A. Improved testability due to tight coupling.
B. Circular dependencies between components.
C. Reduced deployment flexibility.
D. Faster compilation times.
Question 5: Data Flow Management
Which strategies prevent data schema coupling across layers? (Choose two)
A. Sharing DTOs across all layers.
B. Defining layer-specific data models.
C. Using protocol buffers for inter-layer communication.
D. Exposing database entities directly to the UI.
Question 6: Main Component Testing
How can the Main component be tested effectively? (Choose two)
A. Mocking all infrastructure dependencies.
B. Executing end-to-end tests only.
C. Validating dependency injection configurations.
D. Testing business logic within Main.
Question 7: Bootstrapping Dependencies
Which techniques align with Clean Architecture for dependency initialization? (Choose two)
A. Hardcoding concrete implementations in Main.
B. Using a DI container to resolve dependencies.
C. Injecting dependencies via constructor parameters.
D. Embedding configuration logic in domain entities.
Question 8: Layer Isolation Anti-Patterns
Which practices violate layer isolation? (Choose two)
A. UI layer referencing domain interfaces only.
B. Domain layer importing database adapter code.
C. Infrastructure layer implementing domain interfaces.
D. Presentation layer containing business logic.
Question 9: Cross-Layer Communication
Which mechanisms allow safe cross-layer communication? (Choose two)
A. Direct method calls between unrelated layers.
B. Event-driven architecture with domain events.
C. Shared global variables for data transfer.
D. RESTful APIs between bounded contexts.
Question 10: Main Component Minimalism
Why should the Main component remain minimal? (Choose two)
A. To centralize business logic for clarity.
B. To avoid coupling with volatile infrastructure.
C. To simplify deployment configurations.
D. To enable framework-agnostic core logic.
A, C
B, D
A, C
B, C
B, C
A, C
B, C
B, D
B, D
B, D
Example 1: Layered Data Models
// Domain Layer
public class Order {
private String id;
private BigDecimal amount;
// Getters/setters omitted
}
// Infrastructure Layer DTO
public class OrderDocument {
private String _id;
private String amount;
// MongoDB-specific fields
}
// Adapter converting between domain and infrastructure
public class OrderMongoMapper {
public OrderDocument toDocument(Order order) { /*...*/ }
public Order toDomain(OrderDocument doc) { /*...*/ }
}
Example 2: Main Component with DI
public class Main {
public static void main(String[] args) {
// Initialize dependencies
OrderRepository repo = new MongoOrderRepository();
PaymentGateway gateway = new StripeGateway();
// Inject into core component
OrderService service = new OrderService(repo, gateway);
service.processOrder();
}
}
// Domain interface
public interface OrderRepository {
void save(Order order);
}
// Infrastructure implementation
public class MongoOrderRepository implements OrderRepository {
public void save(Order order) {
OrderDocument doc = new OrderMongoMapper().toDocument(order);
// Save to MongoDB
}
}
Example 3: Event-Driven Communication
// Domain event
public class OrderPlacedEvent {
private Order order;
// Constructor/getter omitted
}
// Infrastructure listener
public class EmailNotificationListener {
@Subscribe
public void handle(OrderPlacedEvent event) {
// Send confirmation email
}
}
// Main initializes event bus
public class Main {
public static void main(String[] args) {
EventBus bus = new EventBus();
bus.register(new EmailNotificationListener());
OrderService service = new OrderService(bus);
service.placeOrder(new Order());
}
}