I would like to find a good design pattern on how to implement this example business workflow. Instead of using one giant monolithic procedural-like method call, I was thinking I would like to use a fluent method chaining -- basically, a simple workflow pipeline without using one of those workflow or BPM frameworks. Suggestions on best practice, perhaps a known design pattern?
My Example
- get configuration / user preferences
- validate config/preferences
- look up / standardize additional config/preferences
- get report 1 (with above input)
- get report 2, etc.
- email reports
The inputs/user preferences causes a lot of if/else logic, so I don't want to have my method have to contain all my if/else logic to see if each step was successful or not, and handle. (i.e. I do NOT want)
myOutput1 = CallMethod1(param1, param2, our errorMsg)
if (error)
{ // do something, then break }
myOutput2 = CallMethod2(param1, param2, our errorMsg)
if (error)
{ // do something, then break }
...
myOutput9 = CallMethod9(param1, param2, our errorMsg)
if (error)
{ // do something, then break }
Sample Idea Pipeline code Perhaps something like this? Would it work? How can I improve upon it?
public class Reporter
{
private ReportSettings Settings {get; set;}
private ReportResponse Response {get; set;}
public ReportResponse GenerateAndSendReports(string groupName)
{
ReportResponse response = this.GetInputConfiguration()
.ValidateConfiguration()
.StandardizeConfiguration(groupName)
.PopulateReport1()
.PopulateReport2()
.PopulateReport99()
.EmailReports()
.Output();
return response;
}
public Reporter GetInputConfiguration()
{
this.Response = new ReportResponse();
this.Settings = new ReportSetting();
this.Settings.IsReport1Enabled = ConfigurationManager.GetSetting("EnableReport1");
this.Settings.Blah1 = ConfigurationManager.GetSetting("Blah1");
this.Settings.Blah2 = ConfigurationManager.GetSetting("Blah2");
return this;
}
public Reporter StandardizeConfiguration(string groupName)
{
this.Settings.Emails = myDataService.GetEmails(groupName);
return this;
}
public Reporter PopulateReport1()
{
if (!this.Setting.HasError && this.Settings.IsReport1Enabled)
{
try
{
this.Response.Report1Content = myReportService.GetReport1(this.Settings.Blah1, this.Blah2)
}
catch (Exception ex)
{
this.Response.HasError = true;
this.Response.Message = ex.ToString();
}
}
return this;
}
}
I was thinking of something like this
You are mentioning two distinct concepts: fluent mechanism, and the pipeline (or chain of responsibility) pattern.
Pipeline Pattern
IPipelinewhich containsDoThings();.IPipelinemust contain anIPipeline GetNext();Fluent
IFluent.IValidatedDatacould exposeIStandardizedData Standardize(), andIStandardizedDatacould exposeIStandardizedData PopulateReport(var param)andIStandardizedData PopulateEmail(var param). This is what LINQ does with enumerables, lists, etc.However, in your example it looks like you are mostly looking for a Fluent mechanism. The pipeline pattern helps with data flows (HTTP request handlers, for example). In your case you are just applying properties to a single object
Reporter, so the pipeline pattern does not really apply.For those who ended here because they are looking for a two-way (push and pull) fluent pipeline, you want your fluent actions to build the pipeline, by returning an
IPipelineStep. The behaviour of the pipeline is defined by the implementation of eachIPipelineStep.You can achieve this as follows:
PipelineStepimplementsIPipelineStepPipelineStepcontains aprivate IPipelineStep NextStep(get;set);IPipelineBuildercontains the fluent actions available to build the pipeline.IPipelineStepandIPipelineBuilder.this.NextStep.IPipelineStepcontains avar Push(var input);andvar Pull(var input);Pushdoes things and then callsthis.NextStep.PushPullcallsthis.NextStep.Pulland then does things and returnsYou also need to consider how you want to use the pipeline once built: from top to bottom or the other way around.