The problem we faced was every time an exception was thrown; an email was sent to multiple people. This would result in waking up and having 80 identical emails in our inboxes. I was tasked with stopping this from happening, send only one email per exception. As a Junior Developer, I was very excited at this opportunity, and I just hit the ground running.
There was already an email handler class for me to work with. This class sends emails, very simple.
public class EmailHandler
{
private readonly EmailOptions _emailOptions;
private ILogger _logger;
public EmailHandler(EmailOptions emailOptions, ILogger logger) : base(emailOptions)
{
_logger = logger;
_emailOptions = emailOptions;
}
//send emails (very simple)
}
We also wanted to log the exceptions to a SQL database to be able to see if exceptions were thrown of a similar type. If there was an exception thrown that was the same, we would not send an email. So using Serilog, I created a new class, I will call it SpecialLogger.
public class SpecialLogger
{
private ILogger _msftLogger;
private Serilog.ILogger _seriLogger;
private readonly SqlConnection _connection;
public SpecialLogger(SqlOptions sqlOptions,
ScmtSqlConnectionFactory sqlConnectionFactory)
{
// we don't need XML data
columnOption.Store.Remove(StandardColumn.Properties);
// we do want JSON data.
columnOption.Store.Add(StandardColumn.LogEvent);
_seriLogger = new LoggerConfiguration()
.WriteTo.MSSqlServer(
sqlOptions.Connection,
schemaName: "logs",
tableName: "ApplicationLog",
restrictedToMinimumLevel: LogEventLevel.Verbose)
.CreateLogger();
_connection = sqlConnectionFactory.Create();
}
public void Init(ExecutionContext context, ILogger msftLogger)
{
_msftLogger = msftLogger;
_seriLogger = _seriLogger.ForContext("function",
context.FunctionName);
}
//log errors to SQL database
}
And then I made the necessary changes to the email handler, to replace the use of the traditional logger with the new SpecialLogger.
public class EmailHandler
{
private readonly EmailOptions _emailOptions;
private SpecialLogger _logger;
public EmailHandler(EmailOptions emailOptions,
SpecialLogger logger) : base(emailOptions)
{
_logger = logger;
_emailOptions = emailOptions;
}
//send emails (very simple)
}
Next, I needed to limit the emails and to do that I needed a new class. I will call it EmailLimiter.
public class EmailLimiter
{
private EmailHandler _emailHandler;
private readonly SqlConnection _connection;
public ScmtLoggerEmailHandler(ScmtSqlConnectionFactory
sqlConnectionFactory, EmailHandler emailHandler)
{
_emailHandler = emailHandler;
_connection = sqlConnectionFactory.Create();
}
//limit emails
}
The last step was to add the new EmailLimiter class to the SpecialLogger class to only send emails when an exception was thrown, if that email had not been sent yet.
public class SpecialLogger
{
private EmailLimiter _emailLimiter;
private ILogger _msftLogger;
private Serilog.ILogger _seriLogger;
private readonly SqlConnection _connection;
public SpecialLogger(SqlOptions sqlOptions,
ScmtSqlConnectionFactory sqlConnectionFactory,
EmailLimiter emailLimiter)
{
_emailLimiter = emailLimiter;
// we don't need XML data
columnOption.Store.Remove(StandardColumn.Properties);
// we do want JSON data.
columnOption.Store.Add(StandardColumn.LogEvent);
_seriLogger = new LoggerConfiguration()
.WriteTo.MSSqlServer(
sqlOptions.Connection,
schemaName: "logs",
tableName: "ApplicationLog",
restrictedToMinimumLevel: LogEventLevel.Verbose)
.CreateLogger();
_connection = sqlConnectionFactory.Create();
}
public void Init(ExecutionContext context, ILogger,
context.FunctionName)
{
_msftLogger = msftLogger;_seriLogger =
_seriLogger.ForContext("function", Context.FunctionName);
}
//log errors to SQL database
}
So to recap what I have implemented: EmailHandler requires the SpecialLogger, the SpecialLogger requires the EmailLimiter, and the EmailLimiter requires the EmailHandler.

Naturally, my first idea was to Google the problem I had created and sure enough, Wikipedia had a page on it. Right on brand.

I know what you are thinking, is this a post on how to make circular dependencies? Of course not, here is how it was repaired. First I created an abstract class and pulled out the functions I needed to send emails in both the EmailHandler and the EmailLimiter.
public abstract class NewClass
{
protected readonly EmailOptions EmailOptions;
protected NewClass(EmailOptions emailOptions)
{
EmailOptions = emailOptions;
}
//functions that were the same on EmailLimter and EmailHandler
}
Then I had both the Email Handler and EmailLimiter inherit from this NewClass.
public class EmailHandler : NewClass
{
private readonly EmailOptions _emailOptions;
private SpecialLogger _logger;
public EmailHandler(EmailOptions emailOptions,
SpecialLogger logger) : base(emailOptions)
{
_logger = logger;
_emailOptions = emailOptions;
}
//send emails (very simple)
}
public class EmailLimiter : NewClass
{
private EmailHandler _emailHandler;
private readonly SqlConnection _connection;
public ScmtLoggerEmailHandler(ScmtSqlConnectionFactory
sqlConnectionFactory, EmailHandler emailHandler)
{
_emailHandler = emailHandler;
_connection = sqlConnectionFactory.Create();
}
//limit emails
}
Now I can use the functions I inherited from NewClass to send emails, and I no longer have a circular dependency.