Part 01 · Java Core · 1.1.05.01 · Implementation
Design Patterns / Java & Spring

Từ pattern tới code production

Ví dụ banking/checkout cho thấy pattern bảo vệ variation và boundary ở đâu, đồng thời phân biệt phần Spring đã làm với phần business code vẫn phải thiết kế.

12 chuyên mục nguồn8 code sketchesJava · Spring · ArchitectureBaseline ví dụ mới: Java 21
Cách đọc: đi từ variation ở object tới boundary của framework và hệ thống. Nội dung BK được giữ nguyên; các khối có nhãn Bổ sung tách riêng fact đã đối chiếu, suy luận và đề xuất test. GoF, enterprise patterns và architecture patterns không cùng phạm vi.
Về code: tám ví dụ nguồn là sketches, có type/import/helper chưa khai báo. Ghi chú tại từng chỗ không thay đổi code gốc. Chỉ ví dụ được ghi hoàn chỉnh mới là chương trình chạy độc lập.

Strategy + Registry/Factory

Code nguồn BK · sketch minh họa
interface FeePolicy {
    Money calculate(Transfer transfer);
}

record FeePolicyRegistration(TransferType type, FeePolicy policy) {}

final class FeePolicyRegistry {
    private final Map<TransferType, FeePolicy> policies;

    FeePolicyRegistry(List<FeePolicyRegistration> registrations) {
        policies = registrations.stream().collect(Collectors.toUnmodifiableMap(
            FeePolicyRegistration::type, FeePolicyRegistration::policy));
    }

    FeePolicy forType(TransferType type) {
        return Optional.ofNullable(policies.get(type))
            .orElseThrow(() -> new UnsupportedOperationException(type.name()));
    }
}

Strategy loại conditional phân tán; Registry tập trung mapping. Không phụ thuộc bean name string như business contract nếu domain enum/key rõ hơn.

Bổ sung · API contract và test gợi ý

Fact — Java 21: overload hai mapper của toUnmodifiableMap ném IllegalStateException khi trùng key và từ chối key/value null. Đây là lỗi cấu hình nên phát hiện lúc lắp registry, không phải đợi request đầu tiên. [4]

Đề xuất thiết kế: thêm validation cho registration list, key đầu vào và tập loại transfer phải hỗ trợ. Mapping không sửa được không đồng nghĩa các FeePolicy bên trong thread-safe; giữ policy stateless hoặc xác định cơ chế đồng bộ riêng. Chỉ hot-reload khi có nhu cầu thật và có chính sách thay cả snapshot.

Evidence cần có: test key hợp lệ, key chưa đăng ký, duplicate registration, null input và thiếu một loại bắt buộc. Thêm policy chỉ đổi composition root; không nhân bản switch trong các caller.

Abstract Factory cho một vendor family

Code nguồn BK · sketch minh họa
interface PaymentProviderFactory {
    PaymentClient client();
    PaymentWebhookVerifier verifier();
    PaymentErrorMapper errorMapper();
}

Đây là Abstract Factory vì tạo family components phải tương thích cùng vendor. Nếu chỉ có PaymentClient create(config), đó là factory đơn giản/Factory Method tùy ownership của creation hook.

Bổ sung · Quyết định thiết kế

Boundary: factory cấp một bộ client, webhook verifier và error mapper của cùng vendor. Một factory đơn giản trả duy nhất một client không tự trở thành GoF Abstract Factory; tên class không quyết định intent.

Trade-off: family interface giúp ngăn phối nhầm components nhưng tăng số abstraction phải duy trì. Chỉ dùng khi các product thực sự phải đi cùng nhau; factory cũng cần phân biệt môi trường sandbox/production và cấu hình merchant.

Test gợi ý: tạo từng family rồi chạy cùng contract suite; webhook có chữ ký sai phải bị từ chối, lỗi vendor được dịch nhất quán và không thể vô tình ghép client của A với verifier của B.

Builder cho immutable object

Code nguồn BK · sketch minh họa
TransferRequest request = TransferRequest.builder()
    .sourceAccount(source)
    .destinationAccount(destination)
    .amount(amount)
    .reference(reference)
    .build(); // validate required fields and cross-field invariant

Builder tránh telescoping constructor và làm call site rõ. Với record nhỏ, canonical constructor hoặc named factory đơn giản hơn. Lombok @Builder không tự enforce invariant.

Bổ sung · Invariant ở đâu?

Fact: Lombok sinh builder và đường gọi tạo object; đó không phải bộ luật nghiệp vụ tự động. Đặt required-field và cross-field checks ở constructor/factory được builder gọi, hoặc trong build() do mình kiểm soát. [5]

Đề xuất review: thử cả builder, constructor và named factory, không chỉ happy path của một cách tạo object. Kiểm tra amount, currency, hai tài khoản trùng nhau và collection đầu vào bị sửa sau khi build. Với object nhỏ, chọn record/constructor rõ ràng thay vì thêm builder chỉ để tăng số pattern.

Adapter tại integration boundary

Code nguồn BK · sketch minh họa
interface FraudPort {
    FraudDecision evaluate(Transfer transfer, Duration timeout);
}

final class VendorFraudAdapter implements FraudPort {
    private final VendorSdk sdk;

    public FraudDecision evaluate(Transfer transfer, Duration timeout) {
        try {
            return map(sdk.score(toVendorRequest(transfer), timeout));
        } catch (VendorTimeout ex) {
            return FraudDecision.unknown(ex.requestId());
        }
    }
}

Adapter phải translate model, errors, timeout và unknown outcome. Chỉ đổi method name nhưng để vendor exception/model chảy vào domain chưa tạo anti-corruption boundary.

Bổ sung · Đọc sketch và kiểm tra contract

Giới hạn code nguồn: VendorFraudAdapter chưa có constructor gán final VendorSdk sdk; các type và hàm mapping cũng chưa được khai báo. Đây là sketch về boundary, không phải một file Java tự biên dịch.

Đề xuất contract: tách rejected, retryable failureunknown; unknown không có nghĩa là fraud pass hay payment failed. Caller phải quyết định fail-closed, tạm giữ để review hoặc reconciliation dựa trên yêu cầu nghiệp vụ.

Test gợi ý: response hợp lệ, response không đọc được, vendor timeout, lỗi authentication và request bị xử lý nhưng mất response. Kiểm tra domain không import SDK, timeout/deadline được truyền đúng và log không lộ secret hoặc dữ liệu thanh toán.

Decorator cho behavior có thứ tự

Code nguồn BK · sketch minh họa
FraudPort observed = new MetricsFraudPort(
    new RetryFraudPort(
        new CircuitBreakingFraudPort(vendor)));

Thứ tự thay đổi semantics: retry ngoài circuit breaker khác breaker ngoài retry; metrics ngoài cùng đo user-visible latency. Spring AOP phù hợp concern kỹ thuật, nhưng business-critical ordering nên explicit và test được.

Bổ sung · Ordering có thể quan sát được

Suy luận từ cách lồng wrapper: với Metrics(Retry(Breaker(vendor))), metrics ngoài đo một lời gọi logic; mỗi lần retry lại đi qua breaker. Đổi thành Metrics(Breaker(Retry(vendor))) khiến breaker ngoài nhìn kết quả của cả chuỗi retry. Outcome cụ thể vẫn phụ thuộc contract và cấu hình từng wrapper.

Scope guarantee: circuit breaker không tự giới hạn số lời gọi đồng thời. Tài liệu Resilience4j tách trách nhiệm đó sang bulkhead; sliding-window size không phải concurrency limit. [12]

Test gợi ý: ghi trace enter/exit, số logical calls và số attempts; thử permanent error, transient error, breaker-open và deadline cạn. Bài Practice 1 có chương trình Java kiểm tra hai thứ tự wrapper, không phụ thuộc thư viện resilience.

Chain of Responsibility

Code nguồn BK · sketch minh họa
interface TransferRule {
    Optional<Rejection> check(TransferContext context);
}

for (TransferRule rule : orderedRules) {
    var rejection = rule.check(context);
    if (rejection.isPresent()) return rejection.get();
}

Phù hợp validation/authorization pipeline. Phải định nghĩa order, short-circuit, exception và whether all violations cần collect. Servlet filters và Spring Security filter chain là ví dụ framework.

Bổ sung · Hợp đồng pipeline

Đề xuất: viết rõ rule nào chạy trước, rejection nào kết thúc pipeline và exception nào phải làm cả request thất bại. Đừng biến exception khi kiểm tra authorization thành “không có rejection”. Thu thập mọi lỗi và short-circuit là hai contract khác nhau.

Evidence cần có: rule đầu reject thì rule sau không chạy; pipeline thành công giữ đúng thứ tự; exception không bị nuốt; duplicate rule không gây side effect hai lần. Vòng for nguồn phải nằm trong method có kiểu trả về phù hợp.

Template Method hay composition?

Code nguồn BK · sketch minh họa
abstract class TransferTemplate {
    final Receipt execute(Command command) {
        validate(command);
        var result = perform(command);
        audit(result);
        return result;
    }
    protected abstract Result perform(Command command);
}

Template Method hợp khi framework sở hữu stable lifecycle. Nếu steps cần mix-and-match hoặc test độc lập, inject Strategy/collaborators thường tránh inheritance coupling.

Bổ sung · Chú thích code nguồn và bản hoàn chỉnh

Điểm chưa được nguồn định nghĩa: execute() trả Receipt, còn perform() trả Result, nhưng nguồn chưa cho biết quan hệ hai type. Vì vậy chưa thể kết luận dòng return result; có hợp lệ trong model gốc hay không. validateaudit cũng mới được gọi, chưa có định nghĩa.

Ví dụ bổ sung bên dưới chủ động coi ResultReceipt là hai record độc lập rồi map rõ ràng. Nó minh họa lifecycle trong bộ nhớ; không chứng minh audit và business write cùng một database transaction.

Ví dụ hoàn chỉnh bổ sung · TemplateMethodCheck.java · Java 21
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class TemplateMethodCheck {
    record Command(String id) {}
    record Result(String code) {}
    record Receipt(String code) {}

    abstract static class TransferTemplate {
        private final List<String> trace;

        TransferTemplate(List<String> trace) {
            this.trace = Objects.requireNonNull(trace);
        }

        final Receipt execute(Command command) {
            validate(command);
            Result result = Objects.requireNonNull(perform(command));
            audit(result);
            return new Receipt(result.code());
        }

        private void validate(Command command) {
            if (command == null || command.id() == null
                    || command.id().isBlank()) {
                throw new IllegalArgumentException("command id is required");
            }
            trace.add("validate");
        }

        protected abstract Result perform(Command command);

        private void audit(Result result) {
            trace.add("audit:" + result.code());
        }
    }

    public static void main(String[] args) {
        List<String> trace = new ArrayList<>();
        TransferTemplate service = new TransferTemplate(trace) {
            @Override
            protected Result perform(Command command) {
                trace.add("perform:" + command.id());
                return new Result("ok");
            }
        };
        Receipt receipt = service.execute(new Command("cmd-1"));
        if (!receipt.equals(new Receipt("ok"))
                || !trace.equals(List.of(
                    "validate", "perform:cmd-1", "audit:ok"))) {
            throw new AssertionError(trace);
        }
        System.out.println("template lifecycle: PASS");
    }
}

Lưu thành TemplateMethodCheck.java, chạy hai lệnh bên dưới. Kết quả kỳ vọng: template lifecycle: PASS.

javac --release 21 TemplateMethodCheck.java
java TemplateMethodCheck

Trade-off cần thử: audit thất bại sau perform thì business contract phải xử lý thế nào? Nếu skeleton phải sửa cho mọi subclass hoặc hook chỉ tồn tại để bỏ qua một bước, thử composition trước khi mở rộng hierarchy.

State cho payment lifecycle

Code nguồn BK · sketch minh họa
sealed interface PaymentState permits Pending, Authorized, Captured, Failed {
    PaymentState capture(PaymentContext context);
}

record Authorized(String authorizationId) implements PaymentState {
    public PaymentState capture(PaymentContext context) {
        return context.gateway().capture(authorizationId);
    }
}

State objects làm transition policy rõ hơn switch lớn. Database vẫn cần version/conditional update hoặc lock để hai workers không transition cùng payment; pattern không tự giải quyết concurrency/durability.

Bổ sung · State object không phải transaction

Giới hạn sketch: các implementation Pending, Captured, Failed, PaymentContext và gateway chưa được nguồn cung cấp. Chưa có model cho capture đang xử lý, duplicate command hoặc unknown outcome; không mặc định đồng nhất chúng với Failed.

Đề xuất protocol: lưu command/idempotency key và provider reference, kiểm tra transition hợp lệ, dùng version/conditional update tại database và có recovery cho lời gọi ngoài bị mất response. Optimistic locking trên row sau khi gọi gateway không tự ngăn hai workers cùng gọi gateway trước đó.

Evidence cần có: ma trận legal/illegal edges, hai workers tranh cùng version, command lặp và crash giữa external effect với local update. Phải đối chiếu ledger phía provider; chỉ nhìn state cuối trong database chưa đủ chứng minh không double-charge.

Command và Observer qua distributed boundary

TransferMoneyCommand đóng gói intent để validate/audit/queue, nhưng retry cần idempotency key. Spring application event dùng Observer trong-process và có sync/transaction semantics tùy API/config. Kafka/RabbitMQ có duplicate, ordering và durability; không suy luận guarantee của broker từ Observer pattern.

Bổ sung đã đối chiếu · Event và độ bền

Fact: SimpleApplicationEventMulticaster mặc định gọi listener trên thread của caller; task executor có thể đổi cách dispatch. [8] @TransactionalEventListener mặc định ở AFTER_COMMIT; không có transaction thì không chạy, trừ khi bật fallbackExecution. Hỗ trợ reactive transaction từ Spring 6.1 cần truyền transaction context trong event source. [9]

Suy luận về failure window: chạy sau commit không đồng nghĩa event đã nằm trong durable log. Process có thể chết trước khi external effect hoàn tất. Khi yêu cầu recovery qua restart, dùng protocol có lưu bền và kiểm thử crash, không dựa vào tên Observer.

Fact bổ sung về Outbox: relay có thể publish trùng nếu chết sau publish nhưng trước khi ghi nhận thành công; consumer cần cơ chế xử lý lặp. [13] Đề xuất evidence: giữ stable event ID, retry/reconciliation ledger và thử đúng cửa sổ crash đó.

Repository, Unit of Work và Specification

Bổ sung · Query intent và kiểm chứng

Fact: Spring Data JPA Specification biểu diễn predicate theo Criteria API và hỗ trợ composition. Nó là công cụ xây điều kiện query, không phải bằng chứng query đã tối ưu. [10]

Đề xuất review: đặt transaction boundary ở use case, nói rõ aggregate nào được cập nhật cùng nhau và caller nào được quyền thay state. Kiểm tra SQL thực tế, số round trips, query plan, paging và dữ liệu ngoài transaction; không chỉ mock save() rồi coi đó là chứng minh persistence đúng.

Phân loại: Repository, Unit of Work và Data Mapper thuộc nhóm enterprise/domain-data patterns; Specification trong thiết kế domain và API Specification của Spring Data cần phân biệt. Không đưa cả bốn vào danh sách 23 GoF.

Patterns trong JDK và Spring

API/frameworkPattern lensĐiểm cần nói đúng
ComparatorStrategyOrdering algorithm truyền vào sort; comparator phải giữ contract.
IteratorIteratorTách traversal khỏi representation.
InputStream wrappersDecoratorCùng abstraction, thêm buffering/decompression.
ExecutorsFactoryConvenience creation; vẫn phải hiểu queue/thread/rejection.
Spring IoCDI + FactoryContainer tạo/wire/lifecycle object; DI có scope rộng hơn GoF.
Spring AOP / @TransactionalProxyCall phải đi qua proxy; self-invocation ảnh hưởng behavior.
JdbcTemplateTemplate/callbackTemplate giữ resource/error skeleton, callback cung cấp variation.
Spring MVCFront Controller + MVCDispatcherServlet route qua mappings/adapters.
Bổ sung đã đối chiếu · Đọc framework đúng phạm vi

Proxy mode: lời gọi qua this không đi lại qua Spring AOP proxy. Với class-based proxy, class final không thể bị subclass; method final/private không thể được advise bằng cơ chế override. Không suy rộng hạn chế class-based proxy sang mọi JDK interface proxy hoặc AspectJ weaving. [6]

Đề xuất integration test: lấy bean từ context rồi gọi qua dependency thật, thay vì chỉ new service. Test transaction rollback và đường self-invocation; ghi rõ proxy mode/config trong evidence. Các class final ở sketch là ví dụ Java thuần, không chỉ dẫn bật class-based AOP cho chúng.

Comparator contract: kiểm tra dấu đối xứng, tính bắc cầu và quan hệ của kết quả bằng 0; nếu ordering không consistent với equals, phải hiểu ảnh hưởng khi dùng sorted collections. [1]

Architecture patterns: khác scope GoF

Bổ sung · Decision boundary và checklist
Phân loại theo phạm vi thay đổi — không theo tên class
NhómVí dụ trong bàiKhông tự bảo đảm
GoF / object collaborationStrategy, Adapter, Decorator, State, Command, ObserverDurability, isolation và delivery qua process.
Creation / enterprise / frameworkRegistry, simple factory, DI, Repository, Unit of Work, Data MapperKhông phải mọi ví dụ đều là một trong 23 GoF.
Architecture / distributed workflowHexagonal, CQRS, Event Sourcing, Saga, OutboxKhông có global ACID chỉ nhờ đặt đúng tên pattern.
ResilienceCircuit Breaker, BulkheadTimeout, retry budget, idempotency hoặc đủ capacity.

Fact — Saga: saga phối hợp các local transactions và compensation; không cung cấp isolation như một ACID transaction duy nhất. Compensation là bước nghiệp vụ có thể cần retry, không phải tua ngược mọi external effect. [14]

Checklist thiết kế bổ sung: xác định source of truth; liệt kê local transaction boundaries; mô tả duplicate, out-of-order và unknown outcome; chỉ rõ ai retry/compensate/reconcile; nêu simpler alternative; gắn từng guarantee với một failure test. Với CQRS, ghi trade-off đồng bộ read model; với Event Sourcing, lập kế hoạch schema/replay và data lifecycle.

Nguồn: Java Comparator · Spring container extensions · Spring declarative transactions.

Tài liệu đối chiếu và đọc thêm

Tra cứu ngày 12/09/2026. Java API và ngôn ngữ ghim ở Java 21; tài liệu Spring, Lombok và Resilience4j dùng URL tài liệu hiện hành, không phải cam kết một version cho mọi dự án. Kiểm tra version, proxy mode và cấu hình của ứng dụng trước khi áp dụng.

  1. Java SE 21 · Comparator
  2. Spring · Container Extension Points
  3. Spring · Declarative Transaction Management
  4. Java SE 21 · Collectors.toUnmodifiableMap
  5. Lombok · @Builder
  6. Spring · Proxying Mechanisms
  7. Spring · Bean Scopes
  8. Spring API · SimpleApplicationEventMulticaster
  9. Spring · Transaction-bound Events
  10. Spring Data JPA · Specifications
  11. Java SE 21 · Pattern Matching for switch
  12. Resilience4j · CircuitBreaker
  13. Microservices.io · Transactional Outbox
  14. Microservices.io · Saga