Search

Software Engineer's Notes

Tag

Design Principle

Understanding Three-Phase Commit (3PC) in Computer Science

What is Three-Phase Commit (3PC)?

Distributed systems are everywhere today — from financial transactions to large-scale cloud platforms. To ensure data consistency across multiple nodes, distributed systems use protocols that coordinate between participants. One such protocol is the Three-Phase Commit (3PC), which extends the Two-Phase Commit (2PC) protocol by adding an extra step to improve fault tolerance and avoid certain types of failures.

What is 3PC in Computer Science?

Three-Phase Commit (3PC) is a distributed consensus protocol used to ensure that a transaction across multiple nodes in a distributed system is either committed by all participants or aborted by all participants.

It builds upon the Two-Phase Commit (2PC) protocol, which can get stuck if the coordinator crashes at the wrong time. 3PC introduces an additional phase, making the process non-blocking under most failure conditions.

How Does 3PC Work?

The 3PC protocol has three distinct phases:

1. CanCommit Phase (Voting Request)

  • The coordinator asks all participants if they are able to commit the transaction.
  • Participants check whether they can proceed (resources, constraints, etc.).
  • Each participant replies Yes (vote commit) or No (vote abort).

2. PreCommit Phase (Prepare to Commit)

  • If all participants vote Yes, the coordinator sends a PreCommit message.
  • Participants prepare to commit but do not make changes permanent yet.
  • They acknowledge readiness to commit.
  • If any participant voted No, the coordinator aborts the transaction.

3. DoCommit Phase (Final Commit)

  • After receiving all acknowledgments from PreCommit, the coordinator sends a DoCommit message.
  • Participants finalize the commit and release locks.
  • If any failure occurs before DoCommit, participants can safely roll back without inconsistency.

This three-step approach reduces the chance of deadlocks and ensures that participants have a clear recovery path in case of failures.

Real-World Use Cases of 3PC

1. Banking Transactions

When transferring money between two different banks, both banks’ systems need to either fully complete the transfer or not perform it at all. 3PC ensures that even if the coordinator crashes temporarily, both banks remain consistent.

2. Distributed Databases

Databases like distributed SQL systems or global NoSQL clusters can use 3PC to synchronize data across different data centers. This ensures atomicity when data is replicated globally.

3. E-Commerce Orders

In online shopping, payment, inventory deduction, and order confirmation must all succeed together. 3PC helps reduce inconsistencies such as charging the customer but failing to create the order.

Advantages of 3PC

  • Non-blocking: Unlike 2PC, participants do not remain blocked indefinitely if the coordinator crashes.
  • Improved fault tolerance: Clearer recovery process after failures.
  • Reduced risk of inconsistency: Participants always know the transaction’s current state.
  • Safer in network partitions: Adds a buffer step to prevent premature commits or rollbacks.

Issues and Disadvantages of 3PC

  • Complexity: More phases mean more messages and higher implementation complexity.
  • Performance overhead: Increases latency compared to 2PC since an extra round of communication is required.
  • Still not perfect: In extreme cases (like a complete network partition), inconsistencies may still occur.
  • Less commonly adopted: Many modern systems prefer consensus algorithms like Paxos or Raft instead, which are more robust.

When and How Should We Use 3PC?

3PC is best used when:

  • Systems require high availability and fault tolerance.
  • Consistency is more critical than performance.
  • Network reliability is moderate but not perfect.
  • Transactions involve multiple independent services where rollback can be costly.

For example, financial systems, mission-critical distributed databases, or telecom billing platforms can benefit from 3PC.

Integrating 3PC into Our Software Development Process

  1. Identify Critical Transactions
    Apply 3PC to operations where all-or-nothing consistency is mandatory (e.g., money transfers, distributed order processing).
  2. Use Middleware or Transaction Coordinators
    Implement 3PC using distributed transaction managers, message brokers, or database frameworks that support it.
  3. Combine with Modern Tools
    In microservice architectures, pair 3PC with frameworks like Spring Transaction Manager or distributed orchestrators.
  4. Monitor and Test
    Simulate node failures, crashes, and network delays to ensure the system recovers gracefully under 3PC.

Conclusion

The Three-Phase Commit protocol offers a more fault-tolerant approach to distributed transactions compared to 2PC. While it comes with additional complexity and latency, it is a valuable technique for systems where consistency and reliability outweigh performance costs.

When integrated thoughtfully, 3PC helps ensure that distributed systems maintain data integrity even in the face of crashes or network issues.

Inversion of Control in Software Development

Inversion of Control

What is Inversion of Control?

Inversion of Control (IoC) is a design principle in software engineering that shifts the responsibility of controlling the flow of a program from the developer’s custom code to a framework or external entity. Instead of your code explicitly creating objects and managing their lifecycles, IoC delegates these responsibilities to a container or framework.

This approach promotes flexibility, reusability, and decoupling of components. IoC is the foundation of many modern frameworks, such as Spring in Java, .NET Core Dependency Injection, and Angular in JavaScript.

A Brief History of Inversion of Control

The concept of IoC emerged in the late 1980s and early 1990s as object-oriented programming matured. Early implementations were seen in frameworks like Smalltalk MVC and later Java Enterprise frameworks.
The term “Inversion of Control” was formally popularized by Michael Mattsson in the late 1990s. Martin Fowler further explained and advocated IoC as a key principle for achieving loose coupling in his widely influential articles and books.

By the 2000s, IoC became mainstream with frameworks such as Spring Framework (2003) introducing dependency injection containers as practical implementations of IoC.

Components of Inversion of Control

Inversion of Control can be implemented in different ways, but the following components are usually involved:

1. IoC Container

A framework or container responsible for managing object creation and lifecycle. Example: Spring IoC Container.

2. Dependencies

The objects or services that a class requires to function.

3. Configuration Metadata

Instructions provided to the IoC container on how to wire dependencies. This can be done using XML, annotations, or code.

4. Dependency Injection (DI)

A specific and most common technique to achieve IoC, where dependencies are provided rather than created inside the class.

5. Event and Callback Mechanisms

Another IoC technique where the flow of execution is controlled by an external framework calling back into the developer’s code when needed.

Benefits of Inversion of Control

1. Loose Coupling

IoC ensures that components are less dependent on each other, making code easier to maintain and extend.

2. Improved Testability

With dependencies injected, mocking and testing become straightforward.

3. Reusability

Since classes do not create their own dependencies, they can be reused in different contexts.

4. Flexibility

Configurations can be changed without altering the core logic of the program.

5. Scalability

IoC helps in scaling applications by simplifying dependency management in large systems.

Why and When Do We Need Inversion of Control?

  • When building complex systems with multiple modules requiring interaction.
  • When you need flexibility in changing dependencies without modifying code.
  • When testing is critical, since IoC makes mocking dependencies easy.
  • When aiming for maintainability, as IoC reduces the risk of tight coupling.

IoC is especially useful in enterprise applications, microservices, and modular architectures.

How to Integrate IoC into Our Software Development Process

  1. Choose a Framework or Container
    • For Java: Spring Framework or Jakarta CDI
    • For .NET: Built-in DI Container
    • For JavaScript: Angular or NestJS
  2. Identify Dependencies
    Review your code and highlight where objects are created and tightly coupled.
  3. Refactor Using DI
    Use constructor injection, setter injection, or field injection to provide dependencies instead of creating them inside classes.
  4. Configure Metadata
    Define wiring via annotations, configuration files, or code-based approaches.
  5. Adopt IoC Practices Gradually
    Start with small modules and expand IoC adoption across your system.
  6. Test and Validate
    Use unit tests with mocked dependencies to confirm that IoC is working as intended.

Conclusion

Inversion of Control is a powerful principle that helps developers build flexible, testable, and maintainable applications. By shifting control to frameworks and containers, software becomes more modular and adaptable to change. Integrating IoC into your development process is not only a best practice—it’s a necessity for modern, scalable systems.

Understanding the DRY Principle in Computer Science

What is dry principle?

In software engineering, one of the most valuable design principles is the DRY principle. DRY stands for “Don’t Repeat Yourself”, and it is a fundamental guideline that helps developers write cleaner, more maintainable, and efficient code.

What is the DRY Principle?

The DRY principle was first introduced in the book The Pragmatic Programmer by Andy Hunt and Dave Thomas. It emphasizes that every piece of knowledge should have a single, unambiguous, authoritative representation within a system.

In simpler terms, it means avoiding code or logic duplication. When functionality is repeated in multiple places, it increases the risk of errors, makes maintenance harder, and slows down development.

How Do You Apply the DRY Principle?

Applying DRY involves identifying repetition in code, logic, or even processes, and then refactoring them into reusable components. Here are some ways:

  • Functions and Methods: If you see the same block of code in multiple places, extract it into a method or function.
  • Classes and Inheritance: Use object-oriented design to encapsulate shared behavior.
  • Libraries and Modules: Group reusable logic into shared libraries or modules to avoid rewriting the same code.
  • Configuration Files: Store common configurations (like database connections or API endpoints) in a single place instead of scattering them across multiple files.
  • Database Normalization: Apply DRY at the data level by ensuring information is stored in one place and referenced where needed.

Benefits of the DRY Principle

  1. Improved Maintainability
    When changes are needed, you only update the logic in one place, reducing the chance of introducing bugs.
  2. Reduced Code Size
    Less duplication means fewer lines of code, making the codebase easier to read and navigate.
  3. Better Consistency
    Logic stays uniform throughout the system since it comes from a single source of truth.
  4. Faster Development
    Reusing well-tested components speeds up feature development and reduces time spent debugging.

Main Considerations When Using DRY

While DRY is powerful, it must be applied thoughtfully:

  • Over-Abstraction: Extracting too early or without enough context may lead to unnecessary complexity.
  • Readability vs. Reuse: Sometimes, duplicating a small piece of code is better than forcing developers to chase references across multiple files.
  • Context Awareness: Just because two code blocks look similar doesn’t mean they serve the same purpose. Blindly merging them could create confusion.

Real-World Examples of DRY in Action

  1. Web Development
    Instead of writing the same HTML header and footer on every page, developers use templates or components (e.g., React components, Thymeleaf templates in Spring, or partials in Django).
  2. Database Design
    Instead of storing customer address details in multiple tables, create one address table and reference it with foreign keys. This avoids inconsistency.
  3. API Development
    Common error handling logic is extracted into a middleware or filter instead of repeating the same try-catch blocks in every endpoint.
  4. Configuration Management
    Storing connection strings, API keys, or environment variables in a central config file instead of embedding them across multiple services.

How to Apply DRY in Software Development Projects

  1. Code Reviews
    Encourage teams to identify duplicated code during reviews and suggest refactoring.
  2. Use Frameworks and Libraries
    Leverage well-established libraries to handle common tasks (logging, authentication, database access) instead of rewriting them.
  3. Refactor Regularly
    As projects grow, revisit the codebase to consolidate repeating logic.
  4. Adopt Best Practices
    • Write modular code.
    • Follow design patterns (like Singleton, Factory, or Strategy) when applicable.
    • Use version control to track refactoring safely.
  5. Balance DRY with Other Principles
    Combine DRY with principles like KISS (Keep It Simple, Stupid) and YAGNI (You Aren’t Gonna Need It) to avoid unnecessary abstractions.

Conclusion

The DRY principle is more than just a coding style rule—it’s a mindset that reduces duplication, improves maintainability, and keeps software consistent. By applying it carefully, balancing reuse with clarity, and leveraging it in real-world contexts, teams can significantly improve the quality and scalability of their projects.

Blog at WordPress.com.

Up ↑