Object-oriented programming & transactions

2020-06-03 03:17发布

问题:

A little intro:

Class contains fields and methods (let me skip properties this time).
Fields represent a state of the class.
Methods describe behavior of the class.

In a well-designed class, a method won't change the class's state if it throws an exception, right? (In other words, whatever happens, class's state shouldn't be corrupted)

Question:

Is there a framework, a design pattern, best practice or a programming language to call a sequence of methods in a transactional style, so that either class's state don't get changed (in case of exception), or everything succeeds?

E.g.:

// the class foo is now in the state S1
foo.MoveToState2();
// it is now (supposed to be) in the state S2
foo.MoveToFinalState();
// it is now (supposed to be) in the state, namely, S3

Surely, an exception might occur both in MoveToState2() and MoveToFinalState(). But from this block of code I want the class foo to be either in the state S1 or S3.

This is a simple scenario with a single class involved, no if's, no while's, no side effects, but I hope the idea is clear.

回答1:

Take a look at the Memento pattern

The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback).



回答2:

The simplest and most reliable "pattern" to use here is an immutable data structure.

Instead of writing:

foo.MoveToState2();
foo.MoveToFinalState();

You write:

MyFoo foo2 = foo.MoveToState2();
MyFoo finalFoo = foo2.MoveToFinalState();

And implement the methods accordingly - that is, MoveToState2 does not actually change anything about MyFoo, it creates a new MyFoo that is in state 2. Similarly with the final state.

This is how the string classes in most OO languages work. Many OO languages are also starting to implement (or have already implemented) immutable collections. Once you have the building blocks, it's fairly straightforward to create an entire immutable "entity".



回答3:

Not the most efficient method, but you could have an object that represents your transactional data. When you start a transaction, make a copy of the data and perform all operations on that. When the transaction ends successfully, move the copy to your real data - this can be done using pointers, so need not be too inefficient.



回答4:

Functional programming is a paradigm that seems to fit well to transactional computations. Since no side-effects are allowed without explicit declaration, you have full control of all data flow.

Therefore software transactional memory can be expressed easily in functional terms - See STM for F#

The key idea is the concept of monads. A monad can be used to model an arbitrary computation through two primitives: Return to return a value and Bind to sequence two computations. Using these two, you can model a transactional monad that controls and saves all state in form of continuations.

One could try to model these in an object-oriented way through a State+Memento pattern, but generally, transactions in imperative languages (like the common OO-ones) are much more difficult to implement since you can perform arbitrary side-effects. But of course you can think of an object defining a transaction scope, that saves, validates and restores data as needed, given they expose a suitable interface for this (the patterns I mentioned above).



回答5:

This would be pretty ugly to implement everywhere, but just saving the state locally, then restoring it in the case of an exception would work in simple scenarios. You'd have to catch and rethrow the exception, which may lose some context in some languages. It might be better to wrap it if possible to retain the context.

try {
   save state in local variables
   move to new state
} catch (innerException) {
   restore state from local variables
   throw new exception( innerException )
}


回答6:

When using object copy approach, you have to watch out that the statements to be rolled-back are only affecting the object's or data itself (and aggregates).

But things are getting really difficult if the side-effects of the statements are "more external". For example I/O operations, network calls. You always have to analyze the overall state-changes of your statements.

It gets also really tricky if you touch static data (or evil mutable singletons). Reverting this data isolated is difficult, because other threads could have modified them in between (you could face lost updates).

Reverting/rollback to the past is often not so trivial ;)



回答7:

I would also consider the saga pattern, you could pass a copy of the objects current state into MoveToState2 and if it throws an exception you could catch that internally and use the copy of the original state to rollback. You would have to do the same with MoveToState3 too. If however the server crashed during a rollback you might still get corrupted state, that's why databases are so good.



回答8:

  1. Transactional memory fits here the best.
  2. An option could be a transactional storage. Sample implementation you can find here: http://www.codeproject.com/KB/dotnet/Transactional_Repository.aspx
  3. Memento pattern
  4. Also let me describe a possible pattern on how to implement such behavior: Define a base class TransactionalEntity. This class contains dictionary of properties. All your transactional classes inherit from the TransactionalEntity and should operate over some sort of Dependency Properties/Fields, i.e. properties(getters/setters) which store it's values not in local class fields, but in dictionary, which is stored in the base class. Then you define TransactionContext class. TransactionContext class internally contains a set of dictionaries (one dictionary for each entity that participates in the transaction) and when a transactional entity participates in transaction, it writes all data to the dictionary in the transaction context. Then all you need is basically four methods:

    TransactionContext.StartTransaction(); TransactionalEntity.JoinTransaction(TransactionContext context); //if your language/framework supports Thread Static fields, then you do not need this method TransactionContext.CommitTransaction(); TransactionContext.RollbackTransaction();

To sum up, you need to store state in base class TransactionalEntity and during transaction TransactionalEntity will cooperate with TransactionContext.

I hope, I've explained it well enough.



回答9:

I think a Command Pattern could be well suited to this problem. Linky.



回答10:

I was astonished that no one suggested explicitly the simplest pattern to use .. the State Pattern

In this way you can also eliminate that 'finalState' method and just use 'handle()'. How do you know which final state is? The memento pattern is best used with the Command pattern, and usually applies to GUI operations to implement the undo/redo feature.

Fields represent a state of the class

Fields represents the state of the instanced object. You use many times wrong definitions of the OOP terms. Review and correct.