Structuring code & managing dependencies for better git workflows

Code organisation is a crucial aspect of software development that involves arranging code into logical components and modules to improve maintainability, readability, and reusability.

By organising code effectively, developers can work collaboratively, understand the codebase more easily, and make changes without causing unintended side effects.

Importance of Organising Code into Logical Components and Modules:

Codebases can quickly become large and complex, making it challenging for developers to navigate and modify code. Without proper organisation, the code may become tangled, leading to issues like code duplication, poor readability, and difficulty in identifying specific functionalities.

Organising code into logical components and modules offers the following benefits during development, collaboration/reviews and code maintenance:

  • Maintainability: When code is organised into cohesive modules, making changes or fixing bugs becomes more manageable and less error-prone. Developers can locate the relevant code quickly, reducing debugging time.

  • Readability: Well-organised code is easier to read and understand. Logical grouping and clear naming conventions make it simpler for developers to grasp the purpose of different code sections.

  • Reusability: Code that is well-structured and decoupled can be reused across the project or in other projects, saving development time and effort.

  • Collaboration: In team environments, organised code facilitates collaboration, as developers can work on specific modules without interfering with others' work.

Different Code Organisation Patterns

Several code organisation patterns help developers create maintainable and structured code. Two commonly used patterns are:

  • Modularisation: Modularisation involves breaking down the codebase into smaller, self-contained modules that handle specific tasks or functionalities. Each module encapsulates related code, and communication between modules happens through well-defined interfaces. This approach reduces interdependencies and promotes code reuse.

  • Separation of Concerns: The separation of concerns principle advocates for dividing code into distinct sections, where each section is responsible for a specific aspect of the application. For example, the user interface, data storage, and business logic should be separate concerns, allowing changes in one area without affecting the others.

Examples of Organising Code using Directories and Files

Let's consider an example of organising a simple web application that allows users to create and view posts. We can structure the codebase using directories and files as follows:

my_app/
├── src/
│   ├── components/
│   │   ├── Header.js
│   │   ├── PostList.js
│   │   └── PostForm.js
│   ├── services/
│   │   ├── api.js
│   │   └── validation.js
│   ├── utils/
│   │   ├── date.js
│   │   └── format.js
│   └── app.js
├── public/
│   ├── index.html
│   └── styles.css
└── package.json

In this example:

  • The src directory contains the application's source code.

  • Components are organized within the components directory, each representing a specific UI element.

  • Shared services, such as interacting with the API or data validation, are placed in the services directory.

  • Common utility functions are stored in the utils directory.

  • The entry point of the application is app.js.

  • The public directory contains static assets like index.html and styles.css.

By structuring the code this way, developers can easily locate and work on specific functionalities, promoting a cleaner and more maintainable codebase.


Organising code using clean architecture pattern

Clean Architecture is a software architecture pattern that emphasises separation of concerns and maintaining a clear distinction between business logic, application, and infrastructure layers. Let's consider an example of organising a backend project using Clean Architecture. Here's the folder structure:

my_backend_project/
├── src/
│   ├── app/
│   │   ├── controllers/
│   │   │   ├── user_controller.py
│   │   │   └── post_controller.py
│   │   ├── usecases/
│   │   │   ├── create_user.py
│   │   │   ├── get_users.py
│   │   │   ├── create_post.py
│   │   │   └── get_posts.py
│   │   └── repositories/
│   │       ├── user_repository.py
│   │       └── post_repository.py
│   ├── domain/
│   │   ├── entities/
│   │   │   ├── user.py
│   │   │   └── post.py
│   │   ├── usecases/
│   │   │   ├── create_user.py
│   │   │   └── get_users.py
│   │   ├── interfaces/
│   │   │   ├── user_repository_interface.py
│   │   │   └── post_repository_interface.py
│   │   └── errors/
│   │       ├── validation_error.py
│   │       └── not_found_error.py
│   ├── infrastructure/
│   │   ├── repositories/
│   │   │   ├── user_repository_impl.py
│   │   │   └── post_repository_impl.py
│   │   └── database/
│   │       ├── connection.py
│   │       ├── models/
│   │       │   ├── user_schema.py
│   │       │   └── post_schema.py
│   │       └── migrations/
│   │           ├── user_table_migration.py
│   │           └── post_table_migration.py
│   └── main.py
├── tests/
│   ├── integration/
│   │   ├── test_user_controller.py
│   │   └── test_post_controller.py
│   ├── unit/
│   │   ├── test_create_user.py
│   │   ├── test_get_users.py
│   │   ├── test_user_repository.py
│   │   └── test_post_repository.py
│   └── acceptance/
│       ├── test_create_user.py
│       └── test_get_users.py
├── config/
│   ├── database.py
│   ├── logger.py
│   └── server.py
├── migrations/
│   ├── user_table_migration.py
│   └── post_table_migration.py
└── requirements.txt

In this Python version of the project:

  • The src directory contains the core application code following Clean Architecture principles.

  • The app directory contains the application layer, with controllers and use cases that define application-specific business logic.

  • The domain directory contains the domain layer, including entities, use cases, interfaces, and custom error classes that are independent of any external frameworks or infrastructure.

  • The infrastructure directory houses the infrastructure layer, which includes implementations of repositories and database interactions.

  • The tests directory contains different types of tests organised into integration, unit, and acceptance testing directories.

  • The config directory contains configuration files for the application, such as database and server settings.

  • The migrations directory contains database migration scripts to manage database schema changes.

  • The requirements.txt file lists the project's dependencies.

This folder structure adheres to clean architecture principles by enforcing a clear separation of concerns and dependencies between the different layers of the application. This allows for easy testing, maintainability, and scalability as the project grows.


Clean Architecture with Bounded Context

Using clean architecture with bounded context, the codebase is structured around distinct modules, each representing a specific bounded context within the application. Bounded contexts help define clear boundaries between different parts of the system, allowing teams to work independently and reducing the risk of interference between contexts. Here's an example of organising a Python backend project using Clean Architecture with module structure and bounded context:

my_backend_project/
├── users/
│   ├── src/
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── user_controller.py
│   │   │   └── usecases/
│   │   │       └── create_user.py
│   │   └── domain/
│   │       ├── entities/
│   │       │   └── user.py
│   │       ├── usecases/
│   │       │   └── create_user.py
│   │       ├── interfaces/
│   │       │   └── user_repository_interface.py
│   │       └── errors/
│   │           └── validation_error.py
│   └── tests/
│       ├── integration/
│       │   └── test_user_controller.py
│       └── unit/
│           └── test_create_user.py
├── posts/
│   ├── src/
│   │   ├── app/
│   │   │   ├── controllers/
│   │   │   │   └── post_controller.py
│   │   │   └── usecases/
│   │   │       └── create_post.py
│   │   └── domain/
│   │       ├── entities/
│   │       │   └── post.py
│   │       ├── usecases/
│   │       │   └── create_post.py
│   │       ├── interfaces/
│   │       │   └── post_repository_interface.py
│   │       └── errors/
│   │           └── validation_error.py
│   └── tests/
│       ├── integration/
│       │   └── test_post_controller.py
│       └── unit/
│           └── test_create_post.py
├── shared/
│   ├── src/
│   │   ├── domain/
│   │   │   ├── entities/
│   │   │   │   ├── user.py
│   │   │   │   └── post.py
│   │   │   └── errors/
│   │   │       └── not_found_error.py
│   │   └── infrastructure/
│   │       └── database/
│   │           └── connection.py
│   └── tests/
│       └── unit/
│           ├── test_user.py
│           └── test_post.py
└── main.py

In this example:

  • The project is divided into separate modules, each representing a bounded context, such as users and posts.

  • Each module follows the Clean Architecture structure with its src and tests directories.

  • The shared module contains common code that is shared between different bounded contexts, such as shared entities and errors.

  • Each bounded context (users and posts) has its application logic, domain, and test directories, ensuring that the code within a context remains isolated from others.

  • The shared module contains code that can be shared across different contexts but doesn't directly belong to any single context.

This folder structure helps maintain clear boundaries between different bounded contexts, allowing teams to work independently on their respective modules. Additionally, the shared module facilitates code reuse and reduces duplication across contexts.

Last updated