具有继承性的版本控制服务层

Versioning service layer with inheritance

我有一个 .net core 3.1 api,我想对我的控制器进行版本控制,我认为服务层的版本控制结构如下所示

    public interface IVersionableObject { }
    
    public class GetDataV1 : IVersionableObject { }
    
    public class PostDataV1 : IVersionableObject { }
    
    public class GetDataV2 : IVersionableObject { }
    
    public class PostDataV2 : IVersionableObject { }
    
    public class ListItemV1 : IVersionableObject { }

    public class MobileAppServiceV1
    {
        public virtual async Task<IVersionableObject> Get()
        {
            return new GetDataV1();
        }

    public virtual async Task<IVersionableObject> Post()
    {
        return new PostDataV1();
    }

    public virtual async Task<IVersionableObject> ListItems()
    {
        return new ListItemV1();
    }
}

public class MobileAppServiceV2 : MobileAppServiceV1
{
    public override async Task<IVersionableObject> Get()
    {
        return new GetDataV2();
    }

    public override async Task<IVersionableObject> Post()
    {
        return new PostDataV2();
    }

    [Obsolete("This method is not available for after V1" , true)]
    public async Task<IVersionableObject> ListItems()
    {
        throw new NotSupportedException("This method is not available for after V1");
    }
}

让我们检查控制器

V1 控制器

    [ApiVersion("1.0")]
    [Route("api/{v:apiVersion}/values")]
    public class ValuesControllerV1 : ControllerBase
    {
        private readonly MobileAppServiceV1 _mobileAppServiceV1;

    public ValuesControllerV1()
    {
        _mobileAppServiceV1 = new MobileAppServiceV1();
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return Ok(await _mobileAppServiceV1.Get());
    }

    [HttpGet("listItem")]
    public async Task<IActionResult> ListItems()
    {
        return Ok(await _mobileAppServiceV1.ListItems());
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] string value)
    {
        return Ok(await _mobileAppServiceV1.Post());
    }

}

V2 控制器

    [ApiVersion("2.0")]
    [Route("api/{v:apiVersion}/values")]
    public class ValuesControllerV2 : ControllerBase
    {
        private readonly MobileAppServiceV2 _mobileAppServiceV2;

        public ValuesControllerV2()
        {
            _mobileAppServiceV2 = new MobileAppServiceV2();
        }

        [HttpGet]
        public async Task<IActionResult> Get()
        {
            return Ok(await _mobileAppServiceV2.Get());
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] string value)
        {
            return Ok(await _mobileAppServiceV2.Post());
        }

    }

例如,在 v2 上删除了 ListItems 方法,我避免在具有 Obselete 属性的 v2 上使用 ListItem 方法。

最后我认为结构是这样的,我试着用示例来展示它 code.Can 你给出了一些关于这个结构是否适合网络上的版本控制服务层的想法 api?我愿意接受所有建议。

虽然您当然可以朝这个方向发展,但我不推荐这样做。 继承在我看来并不是思考问题的正确方式。 HTTP 没有继承的概念。要使其发挥作用,存在许多问题和挑战。如果您的目标是共享通用代码,那么您还有其他几种选择,例如:

  • 继承protected 不是动作的方法;适当地用 public 行动公开它们
  • 将尽可能多的非 HTTP 逻辑委托给控制器外部的代码
  • 使用扩展方法或其他类型的扩展来共享内部控制器逻辑

[Obsolete] 属性不会执行您希望它执行的操作。虽然它确实会导致如图所示的编译错误,但为什么不直接删除该方法呢?唯一的边缘情况是,如果您跨多个程序集继承,这会更加复杂。如果完全删除原始代码不是一种选择,那么更好的方法是使用 [NonAction] 修饰过时的方法,使其不再对 ASP.NET.

可见

变体 1

使用受保护的方法共享逻辑。

[ApiController]
public abstract class ApiController : ControllerBase
{
 protected async virtual Task<IActionResult> GetAll(CancellationToken cancellationToken)
 {
    // TODO: implementation
    await Task.Yield();
    return Ok();
 }

 protected async virtual Task<IActionResult> GetOne(int id, CancellationToken cancellationToken)
 {
    // TODO: implementation
    await Task.Yield();
    return Ok();
 }
}

[ApiVersion("1.0")]
[Route("[controller]")]
public class MobileController : ApiController
{
   [HttpGet("list")]
   public Task<IActionResult> Get(CancellationToken cancellationToken) =>
     GetAll(cancellationToken);

   [HttpGet("{id}")]
   public Task<IActionResult> Get(int id, CancellationToken cancellationToken) =>
     GetOne(id, cancellationToken);
}

[ApiVersion("2.0")]
[Route("[controller]")]
public class Mobile2Controller : ApiController
{
   [HttpGet("list")]
   public Task<IActionResult> Get(CancellationToken cancellationToken) =>
     GetAll(cancellationToken);

   [HttpGet("{id:int}")] // new route constraint, but could be alt implementation
   public Task<IActionResult> Get(int id, CancellationToken cancellationToken) =>
     GetOne(id, cancellationToken);
}

变体 2

将非API逻辑移出控制器。

public interface IRepository<T>
{
    IAsyncEnumerable<T> GetAll(CancellationToken cancellationToken);
    Task<T> GetOne(int id, CancellationToken cancellationToken);
}

// TODO: implement IRepository<T>

// NOTE: a generic base class 'could' be used for common logic

[ApiVersion("1.0")]
[ApiController]
[Route("[controller]")]
public class MobileController : ControllerBase
{
   readonly IRepository<MobileApp> repository;

   public MobileController(IRepository<MobileApp> repository) =>
     this.repository = repository;

   [HttpGet("list")]
   public IAsyncEnumerable<MobileApp> Get(CancellationToken cancellationToken) =>
     repository.GetAll(cancellationToken);

   [HttpGet("{id}")]
   public async Task<IActionResult> Get(int id, CancellationToken cancellationToken)
   {
     var model = await repository.GetOne(id, cancellationToken);

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

     return Ok(model);
   }
}

[ApiVersion("2.0")]
[ApiController]
[Route("[controller]")]
public class Mobile2Controller : ControllerBase
{
   readonly IRepository<MobileApp2> repository;

   public Mobile2Controller(IRepository<MobileApp2> repository) =>
     this.repository = repository;

   [HttpGet("list")]
   public IAsyncEnumerable<MobileApp2> Get(CancellationToken cancellationToken) =>
     repository.GetAll(cancellationToken);

   [HttpGet("{id}")]
   public async Task<IActionResult> Get(int id, CancellationToken cancellationToken)
   {
     var model = await repository.GetOne(id, cancellationToken);

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

     return Ok(model);
   }
}

变体 3

使用属性忽略旧方法。我不建议这样做,因为随着时间的推移维护起来会很麻烦。

[ApiVersion("1.0")]
[ApiController]
[Route("[controller]")]
public class MobileController : ControllerBase
{
   [HttpGet("list")]
   public virtual async Task<IActionResult> Get(CancellationToken cancellationToken)
   {
      await Task.Yield();
      return Ok();
   }

   [HttpGet("{id}")]
   public virtual async Task<IActionResult> Get(int id, CancellationToken cancellationToken)
   {
      await Task.Yield();
      return Ok();
   }
}

[ApiVersion("2.0")]
[Route("[controller]")]
public class Mobile2Controller : MobileController
{
   [NonAction] // exclude method as action
   public override Task<IActionResult> Get(int id, CancellationToken cancellationToken) =>
       Task.FromResult<IActionResult>(Ok());

   [HttpGet("{id:guid}")]
   public virtual async Task<IActionResult> GetV2(Guid id, CancellationToken cancellationToken)
   {
      await Task.Yield();
      return Ok();
   }
}

结论

这些只是几种可能性,但还有其他可能性。随着时间的推移,继承往往会使事情变得不清楚和难以管理。您需要 know/remember 哪些属性是继承的,哪些不是。查看源代码或调试时,您可能会跳过多个文件。甚至可能不清楚在哪里设置断点。

您应该预先定义一个众所周知的版本控制策略。一个常见的策略是 N-2 个版本。如果您遵守该政策,那么复制控制器源的想法并不是那么糟糕(例如 3 次)。如上所示,您可以使用多种技术来减少控制器内的代码重复。