0

I'm working on a fairly large but standard dotnet core API. Here's a simplified sample:

// Simplified example of a controller
public class VehiclesController : ControllerBase
{
    private readonly IVehicleService _vehicleService;

    public VehiclesController(IVehicleService vehicleService)
    {
        _vehicleService = vehicleService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetVehicle(int id)
    {
        var vehicle = await _vehicleService.GetVehicleById(id);

        if (vehicle == null)
        {
            return NotFound();
        }

        return Ok(vehicle);
    }

    [HttpPost]
    public async Task<IActionResult> PostVehicle(VehicleModel vehicleModel)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        await _vehicleService.CreateVehicle(vehicleModel);

        return CreatedAtAction(nameof(GetVehicle), new { id = vehicleModel.Id }, vehicleModel);
    }
}

// Example of service interface
public interface IVehicleService
{
    Task<VehicleModel> GetVehicleById(int id);
    Task CreateVehicle(VehicleModel vehicleModel);
}

// Simplified example of service implementation
public class VehicleService : IVehicleService
{
    private readonly IRepository _repository;
    private readonly IMapper _mapper;

    public VehicleService(IRepository repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<VehicleModel> GetVehicleById(int id)
    {
        var vehicle = _repository.GetVehicle(id);
        var vehicleModel = _mapper.Map<VehicleModel>(vehicle);
        return vehicleModel;
    }

    public async Task CreateVehicle(VehicleModel vehicleModel)
    {
        // implementation to create vehicle
    }
}

As per the example, the services all consume and return view models as defined within the API project itself. This is an internal only API used by the front-end.

Now, I'm starting to work on a public API that will be made available for use by outside parties. In total, I'd say there are hundreds of different service methods. I wish to have the ability to reuse any service method on either the internal or external API. In order to support this, I decided to extract the models from the internal API project into a common library.

The problem now is that the models returned from each API could and often should be different - some information available internally should not be exposed externally. My cunning plan for this was to add an interface for each model, then have a private model and public model namespace. Then I would somehow need to identify which API called the service method and return the appropriate mapping:

public interface IVehicleModel {}  

public async Task<IVehicleModel> GetVehicleById(int id)
{
    var vehicle = _repository.GetVehicle(id);
    if (/* determine if internal API is calling */)
    {
        var internalModel = _mapper.Map<Common.InternalModels.Vehilces.Vehicle>(vehicle);
        return internalModel;
    }
    else if (/* determine if external API is calling */)
    {
        var externalModel = _mapper.Map<Common.ExternalModels.Vehicles.Vehicle>(vehicle);
        return externalModel;
    }

    // return null or exception as required
}

This solution will require a ton of refactoring and does not address the issue of these services needing to consume a model. I'm now considering changing the models in common to be service specific models or just DTO's, then having view models in each API and having some form of middleware to map the DTO's to the appropriate API level model.

Is there some specific pattern I could/should look at that I could adapt for my specific situation?

Carel
  • 113
  • 4
  • 1
    Does this answer your question? [Choosing the right Design Pattern](https://softwareengineering.stackexchange.com/questions/227868/choosing-the-right-design-pattern) – gnat Jul 11 '23 at 10:17
  • I would question the reason for trying to reuse this code -- on the surface it sounds like these two projects have different audiences, so attempting to create a shared reusable library risks unwanted coupling between them and potential future complication which could be avoided by not sharing code. If the requirements for these APIs happen to diverge (which is common with projects that have completely separate users/stakeholders), then having both tied to the same shared/reusable code can easily lead to things which perhaps should be separate being shackled together in unwanted ways. – Ben Cottrell Jul 11 '23 at 12:15
  • You could introduce an additional type parameter and pass it along to auto mapper instead of deciding inside the implementation. That said, I think @BenCottrell is right. There are many other ways you could reduce duplication. – Aluan Haddad Jul 13 '23 at 01:26

0 Answers0