Working With Multiple Function Apps In Development Environment

Working With Multiple Function Apps In Development Environment

There is always difference between local development environment and production environment. While working on cloud env, this gap is sometime more then expected and there is not possible to have exact component present in local development environment.

Currently I am working on Microservices and all the different services is separate function app. So when you are working single service then it is simple but as project progress, there is a requirement to integrate between different services.

Following is the example product or cloud structure for components. Here different function app is routed based on urlprefix. Client side only exposed part is api management service url.

image.png

Now in local development there is not a API Management like component.

Let's find alternative and How to overcome from this issue from local development.

  • One thing is sure that there is no API management simulator like storage so we have to look for custom solution.
  • Reverse proxy is the solution for this as per my thinking and it works for me. There is one reverse proxy based on .net and "YARP " and it provides some other functionality as well.

Solution

  1. Create one .net 6 or .net core 3.1 console application.
  2. Add nuget package "YARP.ReverseProxy". ( at the time of writing 1.0.0 version is there but use any latest stable version).
  3. In program.cs configure host like following.
    public class Program
     {
         public static void Main(string[] args)
         { 
             var myHostBuilder = Host.CreateDefaultBuilder(args);
             myHostBuilder.ConfigureWebHostDefaults(webHostBuilder =>
                 {
                     webHostBuilder.UseStartup<Startup>();
                 });
             var myHost = myHostBuilder.Build();
             myHost.Run();
         }
     }
    
  4. and Startup.cs looks like following.

    public class Startup
     {
         public Startup(IConfiguration configuration)
         {
             // Default configuration comes from AppSettings.json file in project/output
             Configuration = configuration;
         }
    
         public IConfiguration Configuration { get; }
    
         // This method gets called by the runtime. Use this method to add capabilities to
         // the web application via services in the DI container.
         public void ConfigureServices(IServiceCollection services)
         {
             // Add the reverse proxy to capability to the server
             var proxyBuilder = services.AddReverseProxy();
             // Initialize the reverse proxy from the "ReverseProxy" section of configuration
             proxyBuilder.LoadFromConfig(Configuration.GetSection("ReverseProxy"));
         }
    
         // This method gets called by the runtime. Use this method to configure the HTTP request 
         // pipeline that handles requests
         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
         {
             if (env.IsDevelopment())
             {
                 app.UseDeveloperExceptionPage();
             }
             // Enable endpoint routing, required for the reverse proxy
             app.UseRouting();
             // Register the reverse proxy routes
             app.UseEndpoints(endpoints =>
             {
                 endpoints.MapReverseProxy();
             });
         }
     }
    
  5. In above startup.cs in configureServices method add AddReverseProxy and also read configuration from appSettings.json. Also in Configure method, MapReverseProxy endpoint is configured.
  6. appSettings.json config like following.

    {
    "Logging": {
     "LogLevel": {
       "Default": "Information",
       "Microsoft": "Warning",
       "Microsoft.Hosting.Lifetime": "Information"
     }
    },
    "AllowedHosts": "*",
    "ReverseProxy": {
     "Routes": {
       "serviceA": {
         "ClusterId": "serviceACluster",
         "Match": {
           "Path": "/api/serviceA/{**catch-all}"
         },
         "Transforms": [
           { "PathPattern": "/{**catch-all}" }
         ]
       },
       "serviceB": {
         "ClusterId": "serviceBCluster",
         "Match": {
           "Path": "/api/serviceB/{**catch-all}"
         },
         "Transforms": [
           { "PathPattern": "/{**catch-all}" }
         ]
       },
       "serviceC": {
         "ClusterId": "serviceCCluster",
         "Match": {
           "Path": "/api/serviceC/{**catch-all}"
         },
         "Transforms": [
           { "PathPattern": "/{**catch-all}" }
         ]
       }
    
     },
     "Clusters": {     
       "serviceACluster": {
         "Destinations": {
           "destination1": {
             "Address": "http://localhost:7071/"
           }
         }
       },
       "serviceBCluster": {
         "Destinations": {
           "destination1": {
             "Address": "http://localhost:7072/"
           }
         }
       },
       "serviceCCluster": {
         "Destinations": {
           "destination1": {
             "Address": "http://localhost:7073/"
           }
         }
       }
     }
    }
    }
    
  7. After this run the application and it will work as expected but before that let's look at the some key point from configuration.

Configuration

image.png

Let's assume that reverse proxy url is : localhost:5000

  • So when you call localhost:5000/api/serviceA/function1 then it call localhost:7071/function1
  • Same apply for other url as well.
  • Another point is that in "YARP" configuration take look at following sample.
    "serviceA": {
          "ClusterId": "serviceACluster",
          "Match": {
            "Path": "/api/serviceA/{**catch-all}"
          },
          "Transforms": [
            { "PathPattern": "/{**catch-all}" }
          ]
    }
    

Here If any thing with prefix /api/serviceA then it look for cluster "serviceACluster". Also in Transforms section it will remove prefix "/api/prefix" before calling backend api so localhost:5000/api/serviceA/function1 transform to localhost:7071/function1. This apply to other prefix.

By this way we get same behavior that we get with API Management at Azure.

Download sample.