5
\$\begingroup\$

I have a requirement to fetch data for different types of Users (Customers, Publishers etc) for three different timelines (retro, current, and past), all have different URLs. There is a scheduler which will run at the different interval (for example 2 minute for retro data, 5 minutes for current data and 10 minutes for Future Dated Data). I have attached an image for better understanding.

enter image description here

 interface IDataPublisher { 
   void GetUrl(); 
   void Publish(); 
 }

  interface ITimeLineGrabber { 
     TimeSpan GetTimeSpanForNextRun(); 
  }

 abstract class BasePublisher : IDataPublisher, ITimeLineGrabber, BackgroundService 
 {
    private readonly ILogger<BasePublisher> _logger;
    public BasePublisher (ILogger<BasePublisher> logger)
    {
        _logger = logger;
    }

    public abstract string GetUserType();
    public abstract string GetDataType();
    public abstract TimeSpan GetTimeSpanForNextRun();
    public abstract string GetUrl();

    public override async Task StartAsync(CancellationToken cancellationToken)
    {
      await base.StartAsync(cancellationToken);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
      await base.StopAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
      var userType = GetUserType();
      var dataType = GetDataType();
      TimeSpan timeSpan = GetTimeSpanForNextRun();
     
      while (!stoppingToken.IsCancellationRequested)
      {
            try
            {
                // Do Something with userType and dataType 
                await Task.Delay(timeSpan , stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error - {ex.Message}", DateTimeOffset.Now);
            }
        }
   }

  public sealed class CustomerRetroPublisher: BasePublisher 
  {
    public override string GetUserType()
    {
       return "Customer";
    }

    public override string GetDataType()
    {
      return "Retro";
    }

    public override Timespan GetTimeSpanForNextRun()
    {
      return Timespan.FromMinutes(2); // read from config file..
    }

    public override string GetUrl()
    {
       return "CustomerRetroUrl";
    }
  }

but this will end up with 12 different classes and loads of code will be duplicated/ over complicated. I have a feeling there must be a simple approach to all this. Just wondering what could be the best approach for my requirement.

\$\endgroup\$
2
  • \$\begingroup\$ Welcome to Code Review! Looks like you already got some answers, great, hope you enjoy your stay! \$\endgroup\$
    – ferada
    Mar 15 '21 at 23:30
  • \$\begingroup\$ Your interface IDataPublisher defines a method Publish(), but I cannot see this method in your implementation of the interface. Is there something missing? \$\endgroup\$
    – SomeBody
    Mar 16 '21 at 12:00
5
\$\begingroup\$

It seems not a good idea to hard code the different constants. I would inject them through the constructor (I am using properties instead of Get-methods):

public sealed class Publisher : interfaces
{
    public void Publisher (string userType, string dataType, Timespan interval, string url)
    {
        UserType = userType;
        DataType = dataType;
        Interval = interval;
        Url = url;
    }

    public string UserType { get; }
    public override string DataType { get; }
    public override Timespan Interval { get; }
    public override string Url { get; }
}

Then create a factory

public static class PublisherFactory
{
    public static Publisher CreateCustomerRetroPublisher()
    {
        return new Publisher("Customer", "Retro", Timespan.FromMinutes(2), "CustRetroUrl");
    }

    ...
}

If you need different behaviors for different publishers, you can inject them this way as well. For instance, inject services for timeline-specific behavior (through a ITimelineService) and user type-specific behavior (through a IUserService). Like this you can combine the different behavior types in different ways. 3 timeline services + 4 user services = 7 services is better than 12 different classes.

The constructor would then have additional parameters

public void Publisher (string userType, string dataType, Timespan interval, string url,
    ITimelineService timeLineService, IUserService userService)

The services can be assigned to private fields, as they do not need to be visible publicly.

See also: Composition over inheritance (Wikipedia)

\$\endgroup\$
1
  • 2
    \$\begingroup\$ This is better than the approach I suggested if the OP doesn't need these types to be modeled as actual C# objects, and only requires string identifiers for use by the scheduling system. It's also better if this type is going to be e.g. serialized for an event bus, or something similar, since it offers a lightweight object focused exclusively on capturing the data necessary to handle the message. \$\endgroup\$ Mar 15 '21 at 22:18
4
\$\begingroup\$

This looks like a good opportunity for a generic interface. E.g.,

interface IDataTimeType<T> where T : IDataType 
{
    T RootData { get; }
}

Then, your time types can be implemented with generics:

class Retro<T> : IDataTimeType<T>
{
    Retro(T rootData) 
    {
        RootData = rootData;
    }
    T RootData { get; }
}

And instantiated like e.g.,

var data = new Data1();
var dataTime = new Retro<Data1>(data);

The interfaces should focus on what interaction points you need to maintain with the objects, not what types each object is; you can use Object.GetType() for that:

var dataTimeType = dataTime.GetType().Name;
var dataType = dataTime.RootData.GetType().Name;

Note: I’d recommend against such general identifiers as Type1 and IDataType, but I’m maintaining them for consistency with your question. Regardless, I assume those are just meant to illustrate the concept while abstracting the question from the specifics of your data model.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Welcome to Code Review! Your post looks good, enjoy your stay! \$\endgroup\$
    – ferada
    Mar 15 '21 at 23:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.