Back to DRY, KISS, YAGNI & Other Principles
Q1Question 1 of 4

The DRY ("Don't Repeat Yourself") principle, how to follow it, and applying it carefully in microservices

Key Concepts
  • DRY: every piece of knowledge should live in exactly one place, so a change is a single safe edit.
  • DRY is about duplicated knowledge, not duplicated characters that happen to look alike.
  • Techniques: extract method, extract class, composition, generics, constants, configuration.
  • Distinguish true duplication from coincidental duplication: merging coincidental similarity creates false abstractions.
  • Within a microservice, DRY is a clear win; across services, some duplication is accepted to preserve independence.
  • Share stable, non-domain utilities as versioned libraries; keep business logic owned by each service.

Definition

Every rule or piece of knowledge should live in exactly one place, so a change is a single safe edit – but only unify things that are truly the same knowledge.
  • DRY stands for Don't Repeat Yourself – every piece of knowledge in a system should have a single, authoritative representation.
  • Goal – when a rule changes, you change it in exactly one place; no hunting through copies.
  • Scope – applies to code, constants, validation rules, data definitions, and business knowledge.
  • Key distinction – DRY is about duplication of knowledge, not merely duplication of characters that happen to look alike.
  • Microservices caution – across service boundaries, some duplication is deliberately accepted to preserve service independence.

Imagine National Bank of India's rule that a minimum balance in a savings account is five thousand rupees. If that number is written in the account-opening screen, again in the withdrawal check, again in the monthly fee calculation, and again in a report, then when the bank changes the minimum to ten thousand, someone has to find and fix all four places – and will surely miss one. DRY says: write that rule once, in one place, and have everyone ask that one place.

Understanding the Concepts

What is the DRY principle?

DRY stands for Don't Repeat Yourself. It states that every piece of knowledge or logic in a system should have a single, unambiguous, authoritative representation. The goal is that when a rule changes, you change it in exactly one place.

JAVA
package com.bank.service;

import java.math.BigDecimal;

// VIOLATION: minimum balance duplicated in three places
public class AccountOpeningService {
    public void open(String name) {
        BigDecimal minBalance = new BigDecimal("5000"); // copy 1
        System.out.println("Min balance required: " + minBalance);
    }
}

public class WithdrawalService {
    public void withdraw(String id, BigDecimal amount, BigDecimal balance) {
        BigDecimal minBalance = new BigDecimal("5000"); // copy 2
        if (balance.subtract(amount).compareTo(minBalance) < 0) {
            throw new IllegalStateException("Below minimum balance");
        }
    }
}

public class MonthlyFeeService {
    public BigDecimal calculateFee(BigDecimal balance) {
        BigDecimal minBalance = new BigDecimal("5000"); // copy 3
        if (balance.compareTo(minBalance) < 0) {
            return new BigDecimal("500");
        }
        return BigDecimal.ZERO;
    }
}

How do you fix DRY violations?

Extract the duplicated knowledge into one authoritative source:

JAVA
package com.bank.model;

import java.math.BigDecimal;

// ONE source of truth for account policy
public class AccountPolicy {
    public static final BigDecimal MIN_SAVINGS_BALANCE = new BigDecimal("5000");
    public static final BigDecimal BELOW_MIN_FEE = new BigDecimal("500");
}
JAVA
package com.bank.service;

import com.bank.model.AccountPolicy;
import java.math.BigDecimal;

public class WithdrawalServiceFixed {
    public void withdraw(String id, BigDecimal amount, BigDecimal balance) {
        if (balance.subtract(amount).compareTo(AccountPolicy.MIN_SAVINGS_BALANCE) < 0) {
            throw new IllegalStateException("Below minimum balance");
        }
        System.out.println("Withdrawn: " + amount);
    }
}

public class MonthlyFeeServiceFixed {
    public BigDecimal calculateFee(BigDecimal balance) {
        if (balance.compareTo(AccountPolicy.MIN_SAVINGS_BALANCE) < 0) {
            return AccountPolicy.BELOW_MIN_FEE;
        }
        return BigDecimal.ZERO;
    }
}

Changing the minimum balance is now a single, safe edit.

What techniques help follow DRY?

  • Extract method – pull repeated statements into a named method.
  • Extract class – pull a repeated cluster of fields and behaviour into its own type.
  • Composition – share behaviour via composition rather than deep inheritance.
  • Generics – one generic repository serves many entity types.
  • Configuration – centralise values that would otherwise be scattered as literals.
JAVA
package com.bank.repository;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

// Generic repository: DRY across entity types
public class InMemoryRepository<T> {
    private final Map<String, T> store = new HashMap<>();

    public void save(String id, T entity) {
        store.put(id, entity);
    }

    public Optional<T> findById(String id) {
        return Optional.ofNullable(store.get(id));
    }
}

// Usage: one class serves Account, Transaction, LoanApplication
// InMemoryRepository<Account> accounts = new InMemoryRepository<>();
// InMemoryRepository<Transaction> txns = new InMemoryRepository<>();

How do you distinguish true duplication from coincidental duplication?

Two code blocks that look identical today but represent different business rules are not real duplication. Merging them creates a false abstraction that becomes painful when the rules diverge.

JAVA
package com.bank.service;

import java.math.BigDecimal;

// These LOOK the same but are different business rules
public class PersonalLoanFee {
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.02")); // personal loan processing fee
    }
}

public class LatePenaltyFee {
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(new BigDecimal("0.02")); // late payment penalty
    }
}

// DO NOT merge them! When the regulator changes the penalty rate
// to 0.03, you do not want the loan fee to change too.

The rule of thumb is "duplicate until it hurts, then abstract." Wait until you have three genuine repetitions that truly change together before unifying.

How should DRY be applied in microservices?

Within a single service, DRY is a clear win. But across microservices, aggressively removing duplication can destroy the very independence that microservices exist to provide.

JAVA
// ANTI-PATTERN: shared library with business logic
// If you change this, ALL services must upgrade and redeploy together
// = distributed monolith
package com.bank.shared;

public class TransferValidator {
    public boolean isValid(String from, String to, java.math.BigDecimal amount) {
        return from != null && to != null && amount.signum() > 0;
    }
}

Practical guidance:

  • Share genuinely stable, generic, non-domain things across services: logging setup, security helpers, serialization utilities.
  • Ship them as versioned libraries so each service upgrades on its own schedule.
  • Do not share business logic or domain models across service boundaries.
  • If two services need the same rule, the owning service exposes it via an API.
JAVA
// BETTER: each service owns its own validation
// PaymentService has its own TransferValidator
// AccountService has its own AccountValidator
// They look similar but evolve independently

What are common mistakes with DRY?

  • Merging two methods that look alike but represent different rules, creating a false abstraction.
  • Creating a shared library for business logic across microservices, creating a distributed monolith.
  • Over-extracting: pulling every three-line method into a utility class, making the code harder to follow.

What is the relationship between DRY and other principles?

  • DRY removes duplication of knowledge.
  • KISS and YAGNI stop you from inventing structure before the knowledge is actually shared.
  • DRY applied too eagerly conflicts with KISS because it tempts premature abstraction.
JAVA
package com.bank.demo;

import com.bank.model.AccountPolicy;
import java.math.BigDecimal;

public class DryDemo {
    public static void main(String[] args) {
        BigDecimal balance = new BigDecimal("3000");
        if (balance.compareTo(AccountPolicy.MIN_SAVINGS_BALANCE) < 0) {
            System.out.println("Below minimum balance of " + AccountPolicy.MIN_SAVINGS_BALANCE);
            System.out.println("Fee: " + AccountPolicy.BELOW_MIN_FEE);
        }
    }
}
TEXT
Below minimum balance of 5000
Fee: 500

What should a senior engineer know?

  • "Duplication is far cheaper than the wrong abstraction" – Sandi Metz.
  • In a banking system, false unification of different regulatory rules is a compliance risk.
  • DRY across microservices is a distributed monolith in disguise.
  • The experienced engineer applies DRY to knowledge, not to characters.

Visual Overview

DIAGRAM
  DRY Violation → Fix
  ────────────────────

  BEFORE (3 copies of min balance = 5000):
  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
  │ AccountOpening   │  │ WithdrawalSvc    │  │ MonthlyFeeSvc    │
  │ minBal = 5000    │  │ minBal = 5000    │  │ minBal = 5000    │
  └──────────────────┘  └──────────────────┘  └──────────────────┘
  Change to 10000 → update 3 places → miss one → BUG

  AFTER (single source of truth):
  ┌──────────────────────────────┐
  │ AccountPolicy                │
  │ MIN_SAVINGS_BALANCE = 5000   │
  └──────────────┬───────────────┘
                 │ referenced by
     ┌───────────┼───────────┐
     ▼           ▼           ▼
  AccountOpening  Withdrawal  MonthlyFee
  Change to 10000 → ONE edit → all correct

Key Takeaway

DRY stands for Don't Repeat Yourself, and it states that every piece of knowledge in a system should have a single, authoritative representation. The point is that when a rule changes, you change it in exactly one place rather than hunting through many copies and inevitably missing one.

At National Bank of India, the minimum-balance rule had been copy-pasted into the withdrawal flow, the monthly-maintenance flow, and the statement generator. When the regulator changed the minimum balance, a developer updated two of the three and missed the third, which produced wrong fees for Priya's account. Rajesh fixed it by creating a single AccountPolicy class that owned those rules, and every flow consulted that policy instead of hard-coding numbers. Changing the minimum balance became a single safe edit.

The nuance Rajesh always raises is distinguishing true duplication from coincidental duplication. Two blocks that look identical today but represent different business rules are not real duplication, and merging them creates a false abstraction that becomes painful when the rules diverge. "Duplication is cheaper than the wrong abstraction," as Sandi Metz says.

In microservices, DRY must be applied carefully because aggressively removing duplication can destroy service independence. Rajesh applies DRY fully within a single service, but across service boundaries he deliberately tolerates some duplication. He only shares stable, non-domain things like logging and security helpers as versioned libraries, and where a rule truly must be consistent across services, the owning service exposes it through an API.

Remember one sentence: DRY means one home for each piece of knowledge, but only unify things that are truly the same knowledge, and across microservices prefer duplication over coupling.

---

Share
Use ← → arrow keys to navigate