CatalogReadRouteContext.java

package com.ecommerce.catalog.infrastructure.datasource;

import java.util.ArrayDeque;
import java.util.Deque;

final class CatalogReadRouteContext {

    private static final ThreadLocal<Deque<Preference>> PREFERENCES =
            ThreadLocal.withInitial(ArrayDeque::new);

    private CatalogReadRouteContext() {
    }

    static Scope preferReplica() {
        return push(Preference.REPLICA);
    }

    static Scope forcePrimary() {
        return push(Preference.PRIMARY);
    }

    static boolean shouldUseReplica() {
        Deque<Preference> preferences = PREFERENCES.get();
        if (preferences.contains(Preference.PRIMARY)) {
            return false;
        }
        return preferences.contains(Preference.REPLICA);
    }

    private static Scope push(Preference preference) {
        PREFERENCES.get().push(preference);
        return new Scope(preference);
    }

    enum Preference {
        PRIMARY,
        REPLICA
    }

    static final class Scope implements AutoCloseable {

        private final Preference preference;
        private boolean closed;

        private Scope(Preference preference) {
            this.preference = preference;
        }

        @Override
        public void close() {
            if (closed) {
                return;
            }
            closed = true;
            Deque<Preference> preferences = PREFERENCES.get();
            Preference current = preferences.poll();
            if (current != preference) {
                preferences.clear();
                PREFERENCES.remove();
                throw new IllegalStateException("Catalog read route scopes closed out of order");
            }
            if (preferences.isEmpty()) {
                PREFERENCES.remove();
            }
        }
    }
}