W
W
WhoMe2018-08-08 22:17:37
Software design
WhoMe, 2018-08-08 22:17:37

How to decorate a composition?

TLDR;

// Как декорировать Connection?
interface Transaction {
    begin()
    commit()
    rollback()
}
interface Connection {
    transaction()
    query(queryString)
}


I am designing a database connection interface (hereinafter referred to as Connection).
I would like to add logging and lazy connection to it (the real connection is only after the first request).
I don't want to shove all this logic into the Connection itself, because the single responsibility principle is violated and logging becomes less convenient (see below).
Initially, there was the following (hereinafter pseudocode):
interface Connection {
    query(queryString)
}

New behavior is easily added by decorators:
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);
    }
}

It's pretty easy to use a logger:
Connection loggedConnection = new LoggedConnection(connection);
// здесь логируется
methodToLog(loggedConnection); 
// loggedConnection.getLog();
// а здесь уже нет
anotherMethod(connection);

Now the problem. The connection needs a transaction manager. I add it to the connection because they are logically connected.
interface Transaction {
    begin()
    commit()
    rollback()
}
interface Connection {
    transaction()
    query(queryString)
}

Now I can't use decorators because the class implementing Transaction will always have a reference to the original Connection and requests from Transaction will not be logged.
PgTransaction implements Transaction {
    PgTransaction(connection) {...}
    begin(){
        this.connection.query("BEGIN");
    }
}
PgConnection implements Connection {
    PgConnection() {
        this.transaction = new PgTransaction(this); // this мы декорировать извне не сможем
    }
}

Is it possible to somehow elegantly make a connection where requests from the transaction manager are also logged, without shoving all the logic into the connection class?
(The code is provided as an example, it can be changed)
Option 1. Solution in the forehead. Not decorable
// Плюс: интуитивно понятная реализация
// Минус: лишняя логика в 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);
}

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

Option 3. Cloning the state of the transaction, with connection substitution
// Плюс: можно декорерировать
// Плюс: удобно вызывать методы транзакции
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();

Answer the question

In order to leave comments, you need to log in

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question