0

I have given the following architecture:


Layers:

Bootstrapper

MyApp.Start

  • IoC-Container etc.

UI Layer

MyApp.Gui

  • Views
  • ViewModels

Business Layer

MyApp.Core

  • DataGenerator
  • DataGeneratorRequest
  • DataGeneratorResponse

MyApp.Core.Interfaces

  • IDataGenerator
  • IDataGeneratorRequest
  • IDataGeneratorResponse

There is a method Generate inside IDataGenerator which takes IDataGeneratorRequest as a parameter and returns IDataGeneratorResponse.

Problem:

At the moment the IoC Container layer can feed my UI with an IDataGenerator, but I would still need to reference the Core-Project to build a request and throw it into the Generate-method whenever it's necessary. I don't like that and would like to get rid of the dependency.

In a perfect world, how would it be architectured?

  1. Was it a good thing to create interfaces for my requests/responses in the first place?
  2. Do the request- and response-classes belong into the Core.Interface-project instead?
  3. Or should I add a third project, which has all classes in it, that are exchanged?
1

Was it a good thing to create interfaces for my requests/Responses in the first place?

That's hard to say. It really depends on how complex your request/response objects are, or if they require some internal hooks to the implementation of your IDataGenerator. It provides the most flexibility.

Do the request- and response-classes belong into the Core.Interface-project instead?

Depends on the answer to the first question. If they have internal hooks to your DataGenerator then no. If they are completely autonomous simple objects, then that would be the simplest option.

Or should I add a third project, which has all classes in it, that are exchanged?

Probably not.


You have some options, and what you choose really depends on how complex your request/response objects are. The first thing that you have to decide for your MyApp.Core.Interfaces project is whether you are going to be strict about everything being a C# interface, or if you can allow simple objects for your request/response. The bottom line is that consumers of your IDataGenerator need to be able to create a request object instance.

Option 1: Keep it all literal interfaces

To do this, you need to add a mechanism to create your request. The simplest way to do that is to either pass in a lambda or provide a IDataGenerator.CreateRequest() method. The new Net Core APIs tend to use the lambda approach:

IDataGeneratorResponse Generate(Action<IDataGeneratorRequest> getRequest);

That would be called something like this:

var response = dataGenerator.Generate(request => {
    request.DataType = DataType.MockReport;
    request.From = DateTime.Now.AddDays(-7);
    request.To = DateTime.Now;
});

The implementation side would look something like this:

public IDataGeneratorResponse Generate(Action<IDataGeneratorRequest> getRequest)
{
    // NOTE: assumption is that DataGeneratorRequest is a class and
    // not a struct

    var request = new DataGeneratorRequest();
    getRequest(request);

    // ... do all the data generation

    return new DataGeneratorResponse(calculatedData);
}

Of course, you can be free to make it more of a fluent API if you so choose. Either approach will work.

Option 2: Keep it logically interfaces

In this case you would either have a fully developed DataGeneratorRequest class, or you would have a factory to create them. I understand that you may not be able to have the full implementation of the DataGeneratorRequest class in your MyApp.Core.Interfaces project, which is why you would need a factory in that case.

The hybrid approach would be to make the base class for DataGeneratorRequest have a protected constructor and a public factory method like this:

public abstract class DataGeneratorRequest : IDataGeneratorRequest
{
    protected DataGeneratorRequest() {};

    // .... the rest of the implementation

    public static IDataGneratorRequest Create()
    {
        // You'll have to use reflection here to create the instance
        // and a way for your full implementation to inform the factory
        // what to instantiate.
    }
}

Option 3: Reference both Interfaces and Implementation

The last option is to rely on the fact that you will have your implementation of the IDataGenerator referenced in your final project as well as the interfaces. You have your DataGeneratorRequest implementation fully developed in the implementation assembly and simply create that like you would any other object:

var request = new DataGeneratorRequest
{
    DataType = DataType.MockReport,
    From = DateTime.Now.AddDays(-7),
    To = DateTime.Now
};

var response = dataGenerator.Generate(request);

This is the simplest to implement, but you won't be able to build things to consume your data generator without referencing the implementation assembly. That might not be how you want to organize your application, particularly if you want to separate the consumers of your interface and load them dynamically.

Tradeoffs

  • Option 1 will ensure that consumers will only need the MyApp.Core.Interfaces assembly to do it's work. It will also make sure that they are designed to consume the same version of the IDataGeneratorRequest object. There's a manageable amount of complexity, and no reflection.
  • Option 2 will either force your DataGeneratorRequest implementation to be very simple, or you will need reflection. In either case, the API is still very usable and will only require a reference to the MyApp.Core.Inferfaces assembly.
  • Option 3 is the easiest to implement, but adds complexity to the consumers of the API. This is primarily because the API is not fully specified in the MyApp.Core.Interfaces assembly, and requires another assembly to be referenced. You loose flexibility, but you might have subtle errors if you have different assemblies for consumers that were built against slightly different versions of the implementation assembly.

To be honest, I am more partial to the first option. It feels more natural to the way Net Core apps are set up and configured these days.

  • Thank you for this brief explaination, it really gave me some ideas. I am really curious about option 1. How would the other side look like, the "Generate" methods implementation, can you add a small simple example of an implementation? I wouldn't even need a request-class anymore, would I? When you talk about "internal hooks", what exactly do you mean by that? – Jannik Jul 2 '18 at 14:20
  • @Jannik, internal hooks would be if there was something that was package public that your Request called directly into your DataGenerator. In other words, it relies on code outside of itself that is only accessible to classes in the same assembly. While it's not ideal, sometimes it does have to be done. – Berin Loritsch Jul 2 '18 at 14:25
  • Unfortunately, I think, I still haven't understood, what an internal hook is :( But I can say that my requests will only be some simple DTOs. :) – Jannik Jul 2 '18 at 14:39
  • 1
    If you make any internal calls, then those are internal hooks: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… . Good news is that if your objects are simple DTOs, you could include them in the MyApp.Core.Interfaces assembly and the request/response interfaces would then be superfluous. – Berin Loritsch Jul 2 '18 at 14:58
  • I have all the Information I needed, I will consider them and will decide whether to move the classes to Core.Interfaces or just try it out for practice to implement your first option. Thank you. – Jannik Jul 2 '18 at 15:07

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.