@WhoMe

Как декорировать композицию?

TLDR;

// Как декорировать Connection?

interface Transaction {
    begin()
    commit()
    rollback()
}
interface Connection {
    transaction()
    query(queryString)
}



Проектирую интерфейс подключения к БД (далее Connection).
Хочется добавить в него логирование и ленивое подключение(реальное подключение только после первого запроса).
Не хочется - пихать всю это логику в сам Connection, ибо нарушается принцип единственной ответственности и логирование становиться менее удобным (см ниже).

Изначально имелось следующее (здесь и далее псевдокод):
interface Connection {
    query(queryString)
}


Новое поведение легко добавляется декораторами:
LoggedConnection implements Connection {
    LoggedConnection(Connection connection) {...}
    query(queryString) {
        this.log.append(queryString);
        return this.connection.query(queryString);
    }
}

LazyConnection implements Connection {
    query(queryString) {
        if (!connected) {
            this.connection.connect();
        }
        return this.connection.query(queryString);
    }
}


Довольно легко можно использовать логгер:

Connection loggedConnection = new LoggedConnection(connection);
// здесь логируется
methodToLog(loggedConnection); 
// loggedConnection.getLog();
// а здесь уже нет
anotherMethod(connection);


Теперь проблема. К соеденению нужен менеджер транзакций. Добавляю его к соединению т.к они логически связаны.
interface Transaction {
    begin()
    commit()
    rollback()
}
interface Connection {
    transaction()
    query(queryString)
}


Теперь я не могу использовать декораторы, потому что класс реализующий Transaction будет всегда иметь ссылку на оригинальный Connection и запросы из Transaction не будут логироваться.

PgTransaction implements Transaction {
    PgTransaction(connection) {...}
    begin(){
        this.connection.query("BEGIN");
    }
}
PgConnection implements Connection {
    PgConnection() {
        this.transaction = new PgTransaction(this); // this мы декорировать извне не сможем
    }
}


Можно ли как-то элегантно сделать соединение где запросы от менеджера транзакции также логируемыми, при этом не пихая всю логику в класс соединения?
(Код предоставлен в качестве примера, его можно менять)

Вариант 1. Решение в лоб. Не декорируемо
// Плюс: интуитивно понятная реализация
// Минус: лишняя логика в Connection
// Минус: надо отлавливать ошибки, чтобы логгер не остался в подключении
interface ConnectionLoggers {
    add(logger)
    remove(logger)
    log(query)
}
Connection {
    // ...
    ConnectionLoggers loggers()
    // ...
}

logger = new ConnectionLogger();
connection.loggers().add(logger);

try { 
    someMethod(connection);
    // logger.getLog();
} finally {
    connection.loggers().remove(logger);
}

Вариант 2. Прокидывать connection при каждом вызове
// Плюс: можно декорерировать
// Минус: Менее удобен при вызове Transaction
// Минус: в begin() технически можно кинуть другое соединение (другое подключене к БД), что может нарушить логику работы
interface Transaction {
    begin(connection)
    commit(connection)
    rollback(connection)
}
// вызов
connection.transaction.begin(connection);

Вариант 3. Клонирование состояния транзакции, с подменой соединения
// Плюс: можно декорерировать
// Плюс: удобно вызывать методы транзакции
interface Transaction {
    begin()
    commit()
    rollback()
    // Создаем новый экземпляр Transaction, с тем же состоянием транзакции, но другим соединением.
    // Оба экземпляра будут разделять одно и то же состояние (в тразакции или нет, уровень вложенности, уровень изоляции).
    // Минус: В clone() технически можно кинуть другое соединение, что может нарушить логику работы.
    // Минус: Интуитивно непонятно как должен быть реализован метод clone()
    // Минус: clone() виден "пользователям" при работе с транзакциями.
    Transaction clone(newConnection);
}
PgTransaction implements Transaction {
    private bool inTransaction;

    PgTransaction(Connection connection, TransactionState state) {

    }
    clone(c) {
        new PgTransaction(c, this.getState())
    }
}
LoggedConnection {
    LoggedConnection(Connection connection) {
        thix.txn = c.transaction().clone(this);
    }
}

// вызов
connection.transaction.begin();

  • Вопрос задан
  • 78 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы