如何在C++中实现整洁架构的组件边界与接口隔离?
Great question—this is one of the trickiest parts of applying Clean Architecture to C++, thanks to the language's compile-time dependency requirements. Let's break this down clearly, focusing on avoiding forbidden dependencies from your business rules (inner layer) to infrastructure (outer layers like databases).
Core Principle Recap
Clean Architecture requires that inner layers (business rules) never depend on outer layers (infrastructure, databases, UI). The only allowed dependencies are outer layers depending on abstractions defined in inner layers.
In C++, this means your business rules must never include header files from your database implementation—doing so would create a forbidden dependency. Here's how to pull this off correctly:
1. Define the Database Abstraction in the Business Rules Layer
Create a pure abstract class (your "Database Interface") inside your business rules module, not in the database module. This abstraction should only use types from the business rules layer, no infrastructure-specific types.
// business_rules/database_interface.h #pragma once #include <string> #include "business_rules/user.h" // Only reference business-domain types class DatabaseInterface { public: virtual ~DatabaseInterface() = default; // Always define virtual destructor for interfaces virtual User get_user_by_id(const std::string& user_id) = 0; virtual void save_user(const User& user) = 0; };
This header is part of your inner layer, and it's the only thing outer layers need to depend on.
2. Implement the Abstraction in the Database Layer
Your database module (outer layer) will depend on the business rules' abstraction. Include the database_interface.h header in your database implementation, and inherit from the abstract class.
// database/postgres_database.h #pragma once #include "business_rules/database_interface.h" #include <pqxx/pqxx> // OK to include database-specific headers here (outer layer) class PostgresDatabase : public DatabaseInterface { private: pqxx::connection db_connection_; public: explicit PostgresDatabase(const std::string& connection_string) : db_connection_(connection_string) {} User get_user_by_id(const std::string& user_id) override; void save_user(const User& user) override; };
// database/postgres_database.cpp #include "postgres_database.h" #include "business_rules/user.h" User PostgresDatabase::get_user_by_id(const std::string& user_id) { // Implement PostgreSQL-specific query logic here pqxx::work txn(db_connection_); pqxx::result res = txn.exec("SELECT id, name FROM users WHERE id = '" + user_id + "'"); txn.commit(); return User(res[0]["id"].as<std::string>(), res[0]["name"].as<std::string>()); } void PostgresDatabase::save_user(const User& user) { // Implement PostgreSQL-specific save logic here pqxx::work txn(db_connection_); txn.exec("INSERT INTO users (id, name) VALUES ('" + user.id() + "', '" + user.name() + "')"); txn.commit(); }
Notice that the business rules layer has zero knowledge of PostgresDatabase—this is key to upholding the Clean Architecture boundary.
3. Use Dependency Injection to Connect Layers
The business rules layer should never create an instance of the database implementation. Instead, a higher-level layer (like an application bootstrap layer) creates the concrete database instance and injects it into the business rules.
Here's how your business service might look:
// business_rules/user_service.h #pragma once #include "database_interface.h" #include "user.h" #include <memory> class UserService { private: std::unique_ptr<DatabaseInterface> database_; public: // Inject the database abstraction via constructor explicit UserService(std::unique_ptr<DatabaseInterface> database) : database_(std::move(database)) {} User fetch_user(const std::string& user_id) { // Only use the abstract interface—no database-specific code here return database_->get_user_by_id(user_id); } void store_user(const User& user) { database_->save_user(user); } };
And the bootstrap code (outermost layer) that wires everything together:
// application/main.cpp #include "business_rules/user_service.h" #include "database/postgres_database.h" #include <memory> int main() { // Create concrete database instance (outer layer responsibility) auto postgres_db = std::make_unique<PostgresDatabase>("dbname=my_db user=postgres"); // Inject it into the business service UserService user_service(std::move(postgres_db)); // Use the business service (no database dependencies here either) User user = user_service.fetch_user("123"); user.set_name("Updated Name"); user_service.store_user(user); return 0; }
Why This Works
- No forbidden dependencies: The business rules layer only references its own abstractions and domain types. It has no idea the PostgreSQL implementation exists.
- Dependency inversion: The outer database layer depends on the inner business layer's abstraction, which aligns perfectly with Clean Architecture's rules.
- Testability: You can easily mock
DatabaseInterfacein unit tests for your business rules, without needing a real database.
内容的提问来源于stack exchange,提问作者eli6

