Reputation: 1114
Basically I have the following design:
public class SessionManager
{
public void BeginSession() {...}
public void CloseSession() {...}
}
public class InvoicingService
{
private SessionManager SessionManager { get; set; }
public InvoicingService(SessionManager sm)
{
SessionManager = sm;
}
public void CreateInvoices(Order order)
{
SessionManager.BeginSession();
// do stuff
SessionManager.CloseSession();
}
}
The issue is that I have to call BeginSession
and EndSession
for each action I want to do in my service(s). I've come up with some solutions to no have to consistently do that every time, but they're very overengineered and I don't like them. How would you folks address this issue? Is there any common design pattern for this?
The main thing that moved me forward to doing the session management within the services was to provide an easy to use set of classes which provide the functionality without having the consumers exploit the interface, by for example opening a session, executing multiple things, and then closing it. Or leaving the session open for too long, etc. But this is a different topic.
Upvotes: 0
Views: 90
Reputation: 330
I think the accepted answer is too simplistic. What if func
breaks, but CloseSession
is still necessary? This seems instead a perfect case for applying a Disposable pattern.
You will have to create a Disposable Session wrapper, but it will be worth the pain.
public class ScopedSession: IDisposable
{
private bool disposed = false;
public ScopedSession()
{
BeginSession();
}
public ~ScopedSession()
{
Dispose(false);
}
public Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private Dispose(bool disposing)
{
if (disposed) return;
if (disposing) CloseSession();
disposed = true;
}
}
public Invoice CreateInvoices(Order order)
{
using (var s = new ScopedSession())
{
// Do stuff
return invoice;
}
}
This will ensure a session is closed even if an exception is thrown.
More info at: https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose
EDIT: The scoped session constructor may receive an ISessionManager in input, so you can make it testable. The test case would pass a mock object implementing the interface, and yes you can hold a private reference to ISessionManager in the class and null it after closing the session.
Upvotes: 1
Reputation: 305
Since "running with session support" is an application wide concern, you might want to look at what Aspect Oriented Programming can do for you. Please see PostSharp
Another approach is to use Decorator pattern. Consider IInvoicingService interface and classes InvoicingService, InvoicingServiceWithSessionSupport (decorator wrapping InvoicingService). But this might very well increase the number of classes. Your solution with helper method seems more elegant.
Upvotes: 0
Reputation: 1114
Like most people suggested, I did it with a helper function:
private T RunWithinSession<T>(Func<T> func)
{
BeginSession();
var result = func();
CloseSession();
return result;
}
public Invoice CreateInvoices(Order order)
{
return RunWithinSession(() =>
{
// Do stuff
return invoice;
});
}
Upvotes: 0