ADVANCED
PROGRAMMING II
LECTURE I –CLEAN
ARCHITECTURE ESSENTIALS
Shynggys Kairatuly Alshyno
MSc in I
[email protected]
OBJECTIVES
Clean Architecture Essentials:
Principles of Clean Architecture (Separation of Concerns,
Dependency Inversion)
Layered Architecture (Entities, Use Cases, Adapters,
Frameworks)
Managing Dependencies and Business Logic Separation
Implementing Domain-Driven Design (DDD) in Golang
Structuring a Golang Project with Clean Code Principles
Examples
Conclusion
LEARNING OUTCOMES
At the end of this lecture, you will be able to:
• Understand the Principles of Clean Architecture
• Explain the importance of Separation of Concerns and Dependency Inversion in software design.
• Identify how these principles contribute to maintainability and scalability.
• Apply Layered Architecture in Software Development
• Differentiate between Entities, Use Cases, Adapters, and Frameworks.
• Organize software components into appropriate layers to promote modularity.
• Manage Dependencies and Business Logic Separation
• Implement dependency inversion to decouple core logic from external systems.
• Use interfaces and dependency injection to maintain clean and testable code.
• Implement Domain-Driven Design (DDD) in Golang
• Define Entities, Value Objects, Aggregates, and Repositories in a DDD approach.
• Structure Go applications following domain-driven best practices.
• Structure a Golang Project with Clean Code Principles
• Design a Go project using best practices for modularity and maintainability.
• Apply a well-organized folder structure to separate concerns.
• Develop and Evaluate Code Examples Based on Clean Architecture
• Write Go programs following Clean Architecture principles.
• Review and critique existing code for architectural improvements
WHAT IS SOFTWARE
ARCHITECTURE?
According to Robert C. Martin (a.k.a Uncle Bob):
Architecture is about Intentional Design
• It defines the boundaries between components and their responsibilities.
• It ensures separation of concerns for maintainability and scalability.
The Purpose of Architecture is to Support Business Logic
• The business rules should be independent of frameworks, databases, or
external systems.
• Good architecture enables easy modifications and testing.
The Primary Goal of Software Architecture is to Minimize Dependencies
• Architecture should ensure that high-level policies (core logic) do not depend
on low-level details (databases, UI, frameworks).
• This follows the Dependency Rule: “Nothing in the inner circle can know
anything at all about something in an outer circle.”
What is Clean Architecture?
Uncle Bob introduced Clean Architecture as a software
design philosophy focused on maintainability, scalability,
and independence from frameworks and external
dependencies.
He defines Clean Architecture as:
• "A set of principles and guidelines that help organize code in a
way that keeps the business logic separate from external
concerns such as UI, databases, and frameworks.“
KEY PRINCIPLES OF CLEAN
ARCHITECTURE
Separation of Concerns
• Different parts of the system should have clear responsibilities.
• Business logic should not depend on external details like databases or frameworks.
Dependency Inversion Rule
• High-level modules (business rules) should not depend on low-level modules (database, UI, frameworks).
• Both should depend on abstractions (e.g., interfaces in Go).
• Example: A repository interface in Go can be implemented with different databases without changing
business logic.
The Layered Structure
Clean Architecture is structured in layers, forming concentric circles:
• Entities (Core Business Rules): The most critical layer, containing enterprise-wide business rules.
• Use Cases (Application Logic): Contains the logic specific to the application’s functionality.
• Adapters (Interface Layer): Converts data between different layers (e.g., controllers, presenters).
• Frameworks & Drivers: Includes databases, UI, third-party libraries, and external APIs.
Rule: The inner layers should never depend on outer layers!This is called the Dependency Rule, where
higher-level logic should not depend on lower-level details.
Independence from External Tools & Frameworks
• The system should be framework-agnostic.
• You should be able to replace databases or UI frameworks without modifying business logic.
• Example: If you switch from PostgreSQL to MongoDB, your business logic remains unchanged.
CLEAN ARCHITECTURE MODEL
STRUCTU
RE
EXAMPLE
ENTITY LAYER
Sometimes can be named as
Core, Domain, Business
Purpose:
• Represents domain models with
business rules that do not
depend on external frameworks
Best Practice:
• Entities should be pure Go
structs with business logic
inside them, not database
concerns.
Bad practice:
• Adding json, bson, db or any
other tags that will add
dependency on something
USECASE LAYER
Sometimes can be named
application logic layer.
Purpose:
• Contains application-specific
business rules, orchestrating how the
system should behave.
Best Practice:
• Use cases should interact only with
abstractions (interfaces), not
concrete implementations.
• Does not depend on specific
databases or UI frameworks.
• Uses dependency injection
(AuthRepo as an interface).
Bad Practice:
• Use cases should not talk directly to
the database.
• Breaks the Dependency Rule by
depending on a specific database
implementation.
ADAPTER LAYER
Sometimes might be called as Interface
Layer.
Purpose:
• Acts as a bridge between external
interfaces (e.g., API, CLI) and the use case
layer.
Best Practice:
• Keep controllers thin—only handle
requests, call use cases, and return
responses.
• Controller only handles HTTP concerns
(request parsing, response writing).
• Delegates logic to the use case layer
(UserUseCase).
• Encapsulates API logic, making it easy to
swap with gRPC or CLI later.
Bad Practice:
• Handler should not contain business logic
or talk to DB directly
• Violates Single Responsibility Principle (SRP)
• Breaks Clean Architecture by bypassing the
use case layer
EXTERNAL LAYER
Layer that contains Frameworks,
drivers and other external stuff.
Purpose:
• Contains external dependencies
(ORMs, HTTP frameworks,
databases).
Best Practice:
• External tools should be confined
to this layer and kept replaceable.
• Encapsulates database logic inside
a repository.
• Implements an interface, so it can
be replaced with another
database.
• Does not affect the business logic
if we change the database*
Bad Practice:
• Direct database queries, global DB
access
MANAGING DEPENDENCIES AND
BUSINESS LOGIC SEPARATION
The Core Idea
• Uncle Bob emphasizes that business logic should remain
independent of external dependencies such as databases,
frameworks, UI, or third-party libraries. The goal is to ensure
that the core of an application is stable, testable, and
reusable, regardless of the technologies used.
Key principles:
• Dependency Inversion Principle (DIP)
• The Dependency Rule
• Separation of Concerns (SoC)
• Use of Interfaces and Dependency Injection (DI)
DEPENDENCY INVERSION PRINCIPLE
(DIP)
Definition:
• “High-level modules should not
depend on low-level modules. Both
should depend on abstractions.”
What does this mean?
• Business logic (high-level code)
should not directly depend on
infrastructure code (low-level
details).
• Both should depend on interfaces
(abstractions).
• This makes it possible to swap
dependencies (e.g., databases, API
clients) without affecting business
logic.
Bad Practice
• Hardcoded Dependency (e.g.
hardcoded queries (like I did before
with snippets, if you remember))
THE DEPENDENCY RULE
Definition:
• "Nothing in an inner circle should know anything at all about
something in an outer circle.“
How does this apply?
• Business logic (core layer) should never depend on
frameworks, databases, or external services.
• Dependencies should flow inwards: infrastructure should
depend on business rules, not vice versa.
SEPARATION OF CONCERNS (SOC)
Definition:
• "Each part of the system should only focus on its
responsibility, without depending on other parts
unnecessarily.“
Practical Example of SoC in a Golang Project: see project
structure example slide
DEPENDENCY INJECTION (DI)
Definition:
• "Dependencies should be
injected instead of being
hardcoded inside classes or
functions.“
Why is DI important?
• Allows flexible dependency
management (e.g., swap
database implementations).
• Improves testability (e.g., inject
mocks for unit tests).
• Reduces coupling between
components (easy to scale
and/or replace).
DOMAIN-DRIVEN DESIGN (DDD)
Definition:
• DDD is a software design approach introduced by Eric Evans,
focusing on modeling business logic through well-defined domain
concepts.
How DDD Aligns with Clean Architecture (Uncle Bob's
Approach)
• Entities & Value Objects: Represent the core business rules.
• Aggregates: Group related entities to maintain consistency.
• Repositories: Provide persistence abstraction.
• Use Cases (Application Services): Encapsulate business workflows.
• Interfaces & Infrastructure: Keep frameworks separate from business
logic.
DOMAIN-DRIVEN DESIGN (DDD)
Entity layer pros & cons:
Pros:
• ✔ Independent of databases, frameworks, or APIs.
• ✔ Contains only domain-specific logic.
• ✔ Can be tested without infrastructure dependencies.
Cons:
• ❌ Needs extra layers (repositories, adapters) to be fully functional.
DOMAIN-DRIVEN DESIGN (DDD)
Repository Interface (Abstraction) pros & cons:
Pros:
• ✔ Prevents business logic from knowing how data is stored.
• ✔ Supports swapping databases (MongoDB, PostgreSQL, etc.)
without changes to the core logic.
Cons:
• ❌ Requires separate implementations for each database type.
DOMAIN-DRIVEN DESIGN (DDD)
Use Case (Application Logic) pros & cons:
• Pros:
• ✔ Keeps business logic separate from data storage.
• ✔ Easily testable with mock repositories.
• ✔ Reusable across different user interfaces (API, CLI, gRPC).
Cons:
• ❌ Requires dependency injection for repository instances.
DOMAIN-DRIVEN DESIGN (DDD)
Repository Implementation (MongoDB) pros & cons:
Pros:
• ✔ Cleanly separates business logic from database interactions.
• ✔ Can swap MongoDB for PostgreSQL by implementing another
repository.
Cons:
• ❌ More boilerplate code compared to directly using an ORM.
DOMAIN-DRIVEN DESIGN (DDD)
Handler (Interface Layer) pros & cons:
Pros:
• ✔ Clean separation of concerns.
• ✔ Works with different frontends (CLI, API, gRPC).
Cons:
• ❌ Requires additional layers (use cases, repositories).
CONCLUSION
Key Takeaways:
Clean Architecture promotes maintainability and flexibility
• By following Separation of Concerns and Dependency Inversion, we keep business logic independent from
frameworks, databases, and external tools.
• This allows us to easily replace or update technologies without breaking core functionality.
A layered approach ensures clear responsibility distribution
• Entities contain core business rules.
• Use Cases define application logic.
• Adapters connect business logic with external tools (APIs, databases).
• Frameworks (databases, UI) remain in the outer layer to avoid direct dependencies.
Managing dependencies improves scalability and testability
• Using interfaces and dependency injection (DI) allows us to swap implementations (e.g., PostgreSQL,
MongoDB) without modifying business logic.
• This enhances unit testing, as we can use mock dependencies instead of real database connections.
DDD provides a structured way to model business logic
• Defining Entities, Value Objects, Aggregates, and Repositories ensures a clear domain model.
• Repositories encapsulate persistence logic, keeping business rules pure and framework-independent.
• Applying Use Cases ensures workflows remain organized and reusable.