Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support interface and implementation classes for API controllers #5431

Open
wing328 opened this issue Apr 19, 2017 · 7 comments
Open

Support interface and implementation classes for API controllers #5431

wing328 opened this issue Apr 19, 2017 · 7 comments

Comments

@wing328
Copy link
Contributor

wing328 commented Apr 19, 2017

Description

For auto-generated server code, we want to generated 2 files for each API controller file:

  1. an interface, which will be overwritten by code generation
  2. an implementation class, which will not be overwritten by code generation

Java Spring has already implemented this:
https:/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/JavaSpring/api.mustache
https:/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/JavaSpring/apiController.mustache

The goal is to avoid application/business logic being overwritten by code generation so that there's less overhead when adding/deleting/updating endpoint definition.

Other server generators should leverage similar design.

Swagger-codegen version

Latest master

Suggest a Fix

If anyone has suggestions for better design or want to work on this enhancement for a particular server generator, please reply to let us know. Thank you.

@radai-rosenblatt
Copy link

it would be nice for java jax-rs if codegen could emit pure jax-rs annotated interfaces for the resources with only the model classes being concrete. that would allow a nice split into a jax-rs interface jar (generated by swagger) while the actual impl would be up to the user.

@jarlesat
Copy link
Contributor

jarlesat commented Aug 21, 2017

As a suggestion I have created an implementation that does this by creating a class JavaJAXRSSpecInterfaceCodegen that inherits JavaJAXRSSpecServerCodegen. It will replace api.mustache whith a new apiInterface.mustache in the list of apiTemplateFiles to process. apiInterface.mustache is a slightly modified version of api.mustache, creating an interface instead of a bare bone implementation. It will also prevent the generation of the file RestApplication.java, and create pom.xml only when wanted (by setting option createPom to true).

Is this a useful approach? The commit can be seen here.

@cdjohnk
Copy link

cdjohnk commented Sep 4, 2018

I would like to do this for the aspnetcore server.

Specifically, I would like the API Controllers to delegate the actions of the various endpoints to interfaces that are defined in the Controllers folder. There would be a new folder called Routes where the implementation classes would be defined. The implementation(s) would be injected in the ConfigureServices method of Startup.cs.

Instead of the default generated behavior being located in the Controller classes, a default Route implementation would be generated and injected. The default Route class would not only provide the desired working no-op API, but it would also serve as a template for implementing the actual route handlers for the working API.

The only thing that would be overwritten when code is regenerated from the API is that Startup.cs would point the Route interfaces back at the default generated Route implementations. There could potentially even be a switch that allows the desired Route implementation to be specified in the codegen configuration.

I am relatively new to this, so I'd welcome feedback on this idea.

@grmcdorman
Copy link

Startup.cs is likely to get customized; for example, if one wants to use https:/Microsoft/aspnet-api-versioning or multiple API endpoints.

C# partial classes seem a better approach, subject to the restriction that partial methods cannot return a value. I have a working implementation doing this; the generated code looks something like this:

    public partial class VersionApiController : Controller
    { 
        /// <summary>
        /// Get the version
        /// </summary>
        /// <response code="200">Successful operation</response>
        [HttpGet]
        [Route("/api/csa/version")]
        [ValidateModelState]
        [SwaggerOperation("GetVersion")]
        [SwaggerResponse(statusCode: 200, type: typeof(VersionInfo), description: "Successful operation")]
        public virtual IActionResult GetVersion()
        {
            IActionResult result = null;
            GetVersion_impl(ref result);
            return result;
        }

        partial void GetVersion_impl(ref IActionResult result);
    }

and the corresponding implementation file (generated in ControllersImpl folder) contains boilerplate for the GetVersion_impl method. You'd add the ControllersImpl folder .swagger-codegen-ignore file to protect your logic implementation in there.

Currently, this is controlled by a configuration flag, which if false (the default) generates codes as it appears today.

I'm waiting on an official corporate account to submit these changes as a pull request.

@grmcdorman
Copy link

Did a bit more investigation. It should be possible to use .Net dependency injection as well; see https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/

Best way of doing this is a bit more work, where two projects are created, one with the implementations of the interfaces, and one (the existing one, essentially) with the interface, routing, and other framework glue such as Startup.cs.

It introduces a new NuGet package, though: NetCore.AutoRegisterDi.

@cdjohnk
Copy link

cdjohnk commented Oct 17, 2018

Here's how I've configured DI for my Controllers, and I've been very happy with my ability to regen code without overwriting my implementations:

In Startup.cs:

public void ConfigureServices(IServiceCollection services)
{

	.
	.
	.
	
	services.AddSingleton<IL2Route, L2Route>();
}

In Controllers/L2Api.cs

namespace IO.Swagger.Controllers
{ 
    /// <summary>
    /// 
    /// </summary>
    public class L2ApiController : Controller
    {
        private readonly IL2Route _l2Route;

        public L2ApiController(IL2Route l2Route)
        {
            _l2Route = l2Route;
        }

        /// <summary>
        /// Find full address details for a given address id
        /// </summary>
        /// <remarks>Retrieve a specific full address record from a specific agency</remarks>
        /// <param name="jurisdiction">jurisdiction being queried for an address record</param>
        /// <param name="adrressid">jurisdiction specific address identifier</param>
        /// <param name="xAPIRouting">API interface routing for request (&#39;hub&#39; or &#39;source&#39;)</param>
        /// <response code="200">successful operation</response>
        /// <response code="400">Invalid ID supplied</response>
        /// <response code="404">Address not found</response>
        [HttpGet]
        [Route("/v1/L2/address")]
        [ValidateModelState]
        [SwaggerOperation("GetAddress")]
        [SwaggerResponse(statusCode: 200, type: typeof(Address), description: "successful operation")]
        public IActionResult GetAddress([FromQuery][Required()]string jurisdiction, [FromQuery][Required()]string addressid, [FromHeader]string xAPIRouting)
        {
            return _l2Route.GetAddress(jurisdiction, addressid, xAPIRouting);
        }
		
		.
		.
		.
		
	}
}

And then I put my implementation classes in a new subfolder called Routes. Here's the interface and implementation code for the L2ApiController code that is being injected in Startup.cs:

In Routes/IL2Route.cs (Route Interface)

namespace IO.Swagger.Controllers
{ 
    /// <summary>
    /// 
    /// </summary>
    public interface IL2Route
    { 
        /// <summary>
        /// Find full address details for a given address id
        /// </summary>
        /// <remarks>Retrieve a specific full address record from a specific agency</remarks>
        /// <param name="jurisdiction">jurisdiction being queried for an address record</param>
        /// <param name="adrressid">jurisdiction specific address identifier</param>
        /// <param name="xAPIRouting">API interface routing for request (&#39;hub&#39; or &#39;source&#39;)</param>
        /// <response code="200">successful operation</response>
        /// <response code="400">Invalid ID supplied</response>
        /// <response code="404">Address not found</response>
        IActionResult GetAddress(string jurisdiction, string addressid, string xAPIRouting);
		
	.
	.
	.
	
	}
}

In Routes/L2Route.cs (Route Implementation)

namespace IO.Swagger.Controllers
{
    /// <summary>
    /// 
    /// </summary>
    public class L2Route : IL2Route
    {
		
        /// <summary>
        /// Find full address details for a given address id
        /// </summary>
        /// <remarks>Retrieve a specific full address record from a specific agency</remarks>
        /// <param name="jurisdiction">jurisdiction being queried for an address record</param>
        /// <param name="addressid">jurisdiction specific address identifier</param>
        /// <param name="xAPIRouting">API interface routing for request (&#39;hub&#39; or &#39;source&#39;)</param>
        /// <response code="200">successful operation</response>
        /// <response code="400">Invalid ID supplied</response>
        /// <response code="404">Address not found</response>
        public IActionResult GetAddress(string jurisdiction, string addressid, string xAPIRouting)
        { 
            //TODO: Uncomment the next line to return response 200 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
            // return StatusCode(200, default(Address));

            //TODO: Uncomment the next line to return response 400 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
            // return StatusCode(400);

            //TODO: Uncomment the next line to return response 404 or use other options such as return this.NotFound(), return this.BadRequest(..), ...
            // return StatusCode(404);

            string exampleJson = null;
            exampleJson = "<null>\n  <streetNumber>aeiou</streetNumber>\n  <streetDirection>aeiou</streetDirection>\n  <streetType>aeiou</streetType>\n  <unitNum>aeiou</unitNum>\n  <city>aeiou</city>\n  <zip>aeiou</zip>\n</null>";
            exampleJson = "{\r\n  \"zip\" : \"zip\",\r\n  \"streetType\" : \"streetType\",\r\n  \"streetNumber\" : \"streetNumber\",\r\n  \"city\" : \"city\",\r\n  \"incidents\" : [ {\r\n    \"incidentId\" : {\r\n      \"identifier\" : \"identifier\",\r\n      \"jurisdiction\" : \"jurisdiction\",\r\n      \"objectType\" : \"objectType\"\r\n    },\r\n    \"incidentDescription\" : \"incidentDescription\"\r\n  }, {\r\n    \"incidentId\" : {\r\n      \"identifier\" : \"identifier\",\r\n      \"jurisdiction\" : \"jurisdiction\",\r\n      \"objectType\" : \"objectType\"\r\n    },\r\n    \"incidentDescription\" : \"incidentDescription\"\r\n  } ],\r\n  \"unitNum\" : \"unitNum\",\r\n  \"people\" : [ {\r\n    \"personName\" : \"personName\",\r\n    \"personId\" : {\r\n      \"identifier\" : \"identifier\",\r\n      \"jurisdiction\" : \"jurisdiction\",\r\n      \"objectType\" : \"objectType\"\r\n    }\r\n  }, {\r\n    \"personName\" : \"personName\",\r\n    \"personId\" : {\r\n      \"identifier\" : \"identifier\",\r\n      \"jurisdiction\" : \"jurisdiction\",\r\n      \"objectType\" : \"objectType\"\r\n    }\r\n  } ],\r\n  \"addressId\" : {\r\n    \"identifier\" : \"identifier\",\r\n    \"jurisdiction\" : \"jurisdiction\",\r\n    \"objectType\" : \"objectType\"\r\n  },\r\n  \"streetDirection\" : \"streetDirection\"\r\n}";
            
            var example = exampleJson != null
            ? JsonConvert.DeserializeObject<Address>(exampleJson)
            : default(Address);
            //TODO: Change the data returned
            return new ObjectResult(example);
        }

		.
		.
		.
	}
}

In my ideal world, the following would be generated by swagger-codegen from my OpenAPI spec in the pattern shown above:

Startup.cs
Controllers/L2Api.cs
Routes/IL2Route.cs
Routes/L2Route.cs (The default no-op implementation to allow the API to work immediately)

Implementing my actual API code would be a matter of writing my own implementation for Routes/IL2Route in the Routes directory, and then changing the route injection in Startup.cs to point to my implementation. Yes, Startup.cs will be overwritten on code generation, but it's so easy to point it to the route implementation that it is not a big deal for me in my current project, and if I forget to do it, I know immediately what the problem is upon testing. The beauty of this is that my business logic is completely separated into it's own class from the generated code, all in one nice clean project. I can actually write multiple implementations if I like, and switch from one to the other just by changing which one is injected in Startup.cs. If I change my OpenAPI spec, I may wind up with my implementation being incompatible with the interface, but again it will be crystal clear what I have to fix to get the project to compile again.

As I say, I'm using this approach and I'm liking it so far. It's working beautifully for me.

@lhuria94
Copy link

lhuria94 commented Dec 3, 2021

Hi, Just jumping this at the top of issues, do we have a solution for this?
I can't seem to find any examples of interfaces being generated for Lumen, node for server stubs.

A bit related: https://stackoverflow.com/questions/42194550/where-to-add-server-logic-using-swagger-codegen-with-lumen-server-stub

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment