My suggestion is the adoption of the standard Work Unit (Unit of Work). Its definition is basically:
Maintains a list of objects affected by a transaction of business objects and coordinates the writing of changes and the resolution of competition problems.
I’m going to give you a simplified example of implementing this pattern here, I know it’s much more complex and extensive, but I think it will meet your needs. I will be using the repository pattern along as well, but I believe it is easily adaptable to other data access standards like DAL.
We start by defining an interface for the Work Unit. This is because access to different databases or different forms of data access (direct ADO.Net or ORM) would change the concrete implementation of the Transfer Unit.
Public Interface IUnidadeTrabalho
Inherits IDisposable
Sub Commit()
Sub Rollback()
Function CreateCommand() As IDbCommand
End Interface
Then, to instantiate the Work Unit, we use a Factory, in which we have already defined the connection and make it ready for use.
Public Class UnidadeTrabalhoFactory
Public Shared Function Criar() As IUnidadeTrabalho
Dim conexao As MySqlConnection = New MySqlConnection("MinhaConexao")
conexao.Open()
Return New UnidadeTrabalhoAdo(conexao, True)
End Function
End Class
And here the concrete implementation of the Work Unit, using ADO.Net. Its function is quite simple: It takes the connection by parameter and opens a transaction. From there, the control of what will or will not be executed in the database depends on it, with the actions Commit
or Rollback
. She is also responsible for returning the DbCommand
which will be executed at the bank, which will already be linked to the open transaction:
Public Class UnidadeTrabalhoAdo
Implements IUnidadeTrabalho
Private _conexao As IDbConnection
Private _transacao As IDbTransaction
Public Sub New(ByRef conexao As IDbConnection)
Me._conexao = conexao
Me._transacao = conexao.BeginTransaction()
End Sub
Public Function CreateCommand() As IDbCommand Implements IUnidadeTrabalho.CreateCommand
Dim command As IDbCommand = _conexao.CreateCommand()
command.Transaction = _transacao
Return command
End Function
Public Sub Commit() Implements IUnidadeTrabalho.Commit
If (IsNothing(_transacao)) Then
Throw New InvalidOperationException("Não há transação aberta.")
End If
_transacao.Commit()
_transacao = Nothing
End Sub
Public Sub Rollback() Implements IUnidadeTrabalho.Rollback
If (IsNothing(_transacao)) Then
Throw New InvalidOperationException("Não há transação aberta.")
End If
_transacao.Rollback()
_transacao = Nothing
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
If (Not IsNothing(_transacao)) Then
_transacao.Dispose()
_transacao = Nothing
End If
If (Not IsNothing(_conexao)) Then
Me._conexao.Close()
Me._conexao = Nothing
End If
End Sub
End Class
Here I am suggesting the repository implementation for your business. The work unit is injected via the manufacturer and the seat control must be made from it.
Public Class EstoqueRepositorio
Private _unidadeTrabalho As IUnidadeTrabalho
Public Sub New(ByRef unidadeTrabalho As IUnidadeTrabalho)
Me._unidadeTrabalho = unidadeTrabalho
End Sub
Function DarBaixaNoEstoque()
Dim command As IDbCommand = Me._unidadeTrabalho.CreateCommand()
command.CommandText = "INSERT INTO ..."
REM código aqui
End Function
End Class
Public Class CaixaRepositorio
Private _unidadeTrabalho As IUnidadeTrabalho
Public Sub New(ByRef unidadeTrabalho As IUnidadeTrabalho)
Me._unidadeTrabalho = unidadeTrabalho
End Sub
Function AdicionarDinheiroAoCaixa()
REM código aqui
End Function
End Class
Public Class ClienteRepositorio
Private _unidadeTrabalho As IUnidadeTrabalho
Public Sub New(ByRef unidadeTrabalho As IUnidadeTrabalho)
Me._unidadeTrabalho = unidadeTrabalho
End Sub
Function AdicionarProdutoAoCliente()
REM código aqui
End Function
End Class
And here, finally, where everything really happens. When calling the sell action, a new instance of the Work Unit should be created and injected into each of the repositories. While the method Commit
of the Work Unit is not called, nothing will be sent to the bank. If there is an error, the block Catch
will be the place to do the Rollback
. Finally, it should be called the Dispose
to release the connection.
Public Sub Vender()
Dim unidadeTrabalho As IUnidadeTrabalho = UnidadeTrabalhoFactory.Criar()
Try
Dim repositorioEstoque = New EstoqueRepositorio(unidadeTrabalho)
Dim repositorioCaixa = New CaixaRepositorio(unidadeTrabalho)
Dim repositorioCliente = New ClienteRepositorio(unidadeTrabalho)
repositorioEstoque.DarBaixaNoEstoque()
repositorioCaixa.AdicionarDinheiroAoCaixa()
repositorioCliente.AdicionarProdutoAoCliente()
unidadeTrabalho.Commit()
Catch ex As Exception
unidadeTrabalho.Rollback()
Finally
unidadeTrabalho.Dispose()
End Try
End Sub
How is the bank being accessed? Are you using any ORM, such as Entity Framework or Nhibernate? I ask because I believe that a solution to your case is the use of a Pattern called Unit of Work, which would be much easier to implement with the use of a ORM.
– Marcus Vinicius
I’m not using any of these tricks, I just created a class with these functions... The access to the database is by code same, no ORM... in fact the one I posted is an example, I will start a project from scratch and need to gather ideas to get more security and performance. I will study a little about the pattern you indicated to me. Thanks! But I would still like more opinions, several ways to do the same thing... I want to take advantage of as much information as possible ;)
– Wesley Silva
I was thinking of making the functions return a Boolean saying whether or not they managed to pass the values to the database... And then, in case any of this went wrong I would reverse the others... but I think it would be a very masonry method, I don’t think it’s the best option
– Wesley Silva
I’m coming up with an implementation of Mysql and VB.NET Work Unit, when I finish I put here.
– Marcus Vinicius
Okay, thank you very much Marcusvinicius! ?
– Wesley Silva