Micro APIs with OpenFaaS and .NET

Goncalo Oliveira
ITNEXT
Published in
6 min readFeb 4, 2021

--

Serverless functions are becoming increasingly popular, but not everything fits into that model. And although it is possible to create an API with a function for every operation on a resource, it would easily become something difficult to manage.

Luckily, OpenFaaS can host various types of workloads, not just functions, as long as they serve HTTP traffic, assume ephemeral storage, and be stateless. That means that we can, if we want (not that we should), bundle an entire API behind a workload.

Micro APIs

The idea behind a micro API is to have a microservice that implements an API for a particular context — usually one or a very limited set of resources. That particular context ensures you don’t duplicate too much code and that you focus the behaviour of the microservice.

The rule of thumb is that it should be small enough to focus only on what matters (the context) and just big enough to avoid code duplication and fragmentation (beyond the context).

If our context is Users for example, then the micro API should focus on that only. On the other hand, if our context is Posts in a blog, it might be that the micro API deals with both posts and comments on posts — or we might have two separate micro APIs.

As an example, let’s consider we want to manage notes on a To Do list. Our context is the notes and we need the following operations:

  • Get a list of notes
  • Retrieve an existing note
  • Add a new note
  • Change an existing note
  • Delete a note

Our micro API will implement the notes resource and the above operations. Nothing else.

The Environment

This article doesn’t focus on setting up a working OpenFaaS cluster and assumes you already have one or know how to set one up. There are a multitude of options to do so, from managed kubernetes clusters, to minikube or faasd. You can have a look at this article to learn how to set up an environment with minikube.

ASPNET Functions

We could create a Web API project with .NET and host it using a Dockerfile, but that would mean writing the Dockerfile and all the project boilerplate for every micro API. Therefore, we are going to use ASPNET Functions templates that handles all of that and recently added a new controller template just right for these scenarios.

Installation

The templates are installed with the faas-cli, pulling them from the git repository.

$ faas-cli template pull https://github.com/goncalo-oliveira/faas-aspnet-template

This installs three templates:

  • aspnet
  • aspnet-controller
  • aspnet-fsharp

The aspnet and aspnet-fsharp templates are designed for functions, focused on doing one single operation. The aspnet-controller template however, implements a Controller class, allowing us to implement multiple operations.

Creating our micro API

Let’s start by creating our workload with the aspnet-controller template.

$ faas-cli new --lang aspnet-controller notes

Looking at out notes folder, we can see a Controller.cs and a Startup.cs files. The Startup.cs is essentially empty, because all the boilerplate is handled elsewhere. In here, we will add only what relates to our project.

The Controller.cs is essentially an Hello World, almost a blank page.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace OpenFaaS
{
[ApiController]
[Route("/")]
public class Controller : ControllerBase
{
[HttpGet]
public Task<IActionResult> GetAsync()
{
var result = new
{
Message = "Hello!"
};
return Task.FromResult<IActionResult>( Ok( result ) );
}
}
}

Before we continue, let’s run our project locally. There are a few ways to do that, but we’ll do it with FaaS Runner, because it will be useful for debugging.

$ cd notes
$ dotnet build
$ faas-run bin/Debug/netstandard2.0/function.dll
OpenFaaS ASPNET Function Loader
To debug attach to process id 1705.Loaded function assembly bin/Debug/netstandard2.0/function.dll.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:9000

By default it runs on port 9000, but we can change that with the --port flag. Let’s try our service.

$ curl localhost:9000
{"message":"Hello!"}

Implementing the operations

We start by creating a Note.cs file for the structure of our notes.

using System;namespace OpenFaaS
{
public class Note
{
public string Id { get; set; }
public string Content { get; set; }
}
}

Since this is only for educational purposes, we are not storing the notes on a physical storage and therefore, just keeping them in-memory while the service is running with a static dictionary. We’re not troubling ourselves with concurrency either. We’re also dropping the async since there are no tasks, to improve the readability of the code.

public class Controller : ControllerBase
{
private static readonly IDictionary<string, Note> dictionary = new Dictionary<string, Note>();

Now let’s implement our operations. We start with the list of notes. This operation will return only the ids of the notes and not their contents. We delete the existing GetAsync method and add the following:

[HttpGet]
public IActionResult Get()
{
var notes = dictionary.Values.Select( note => new Note
{
Id = note.Id
} )
.ToArray();
if ( !notes.Any() )
{
return NoContent();
}
return Ok( notes );
}

Now the operation to retrieve a single note, where we need to add a route template.

[HttpGet( "{id}" )]
public IActionResult GetSingle( string id )
{
if ( !dictionary.TryGetValue( id, out var note ) )
{
return NotFound();
}
return Ok( note );
}

Nothing here is different from what we would do with a Web API. Let’s add the remaining operations.

[HttpPost]
public IActionResult Post( [FromBody] Note note )
{
note.Id = Guid.NewGuid().ToString();
dictionary.Add( note.Id, note ); // we should return a 201 Created response here
// but let's keep it simple as it's not the
// purpose of the article
return Ok( note );
}
[HttpPut( "{id}" )]
public IActionResult Put( string id, [FromBody] Note note )
{
if ( !dictionary.ContainsKey( id ) )
{
return NotFound();
}
note.Id = id; dictionary[id] = note; return Ok( note );
}
[HttpDelete( "{id}" )]
public IActionResult Delete( string id )
{
if ( !dictionary.ContainsKey( id ) )
{
return NotFound();
}
dictionary.Remove( id ); return NoContent();
}

The entire source code can be found on GitHub.

Testing it out

That’s it. Let’s build it and run it locally.

$ dotnet build$ faas-run bin/Debug/netstandard2.0/function.dll

And let’s try it out. We’ll first insert two notes. I’m using curl on Linux, but you can use Postman or something else.

$ curl -X POST -H "Content-Type: application/json" \
-d '{"content": "this is my first note"}' \
localhost:9000
{"id":"37b3ad6a-2f60-49aa-95c8-9fd7cdac0e3f","content":"this is my first note"}
$ curl -X POST -H "Content-Type: application/json" \
-d '{"content": "a second note"}' \
localhost:9000
{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6","content":"a second note"}

We should have two notes now. Let’s list them.

$ curl localhost:9000
[{"id":"37b3ad6a-2f60-49aa-95c8-9fd7cdac0e3f"},{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6"}]

Let’s retrieve our second note using its id.

$ curl localhost:9000/c98d058d-e24e-4b23-907a-9a2d4d298ad6
{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6","content":"a second note"}

Now we’ll update the second note and retrieve it again.

$ curl -X PUT -H "Content-Type: application/json" \
-d '{"content": "still my second note"}' \
localhost:9000/c98d058d-e24e-4b23-907a-9a2d4d298ad6
{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6","content":"still my second note"}
$ curl localhost:9000/c98d058d-e24e-4b23-907a-9a2d4d298ad6
{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6","content":"still my second note"}

The only operation missing is to delete a note. Let’s delete the first.

$ curl -X DELETE localhost:9000/37b3ad6a-2f60-49aa-95c8-9fd7cdac0e3f$ curl localhost:9000
[{"id":"c98d058d-e24e-4b23-907a-9a2d4d298ad6"}]

Multiple Controllers

Although the template was clearly designed to have a single controller, it’s only by concept and not a restriction. If our context requires more than one controller, we can do so. We can even delete the Controller.cs file and add our own controllers, such as CarsController.cs and ModelsController.cs for example — all the same as we would do with a regular Web API.

Just remember that if you start adding things that aren’t really about a particular context, then it’s probably no longer a micro API.

Final Words

The concept of micro APIs is an interesting one that sits somewhere between serverless functions and microservices. Due to the ability of OpenFaaS to run different workloads other than functions, we can use the same tools and collect the benefits of it.

Make sure you clap if you liked it and feel free to leave a comment. Also don’t forget to star the OpenFaaS and ASPNET Functions projects on GitHub to show your support.

--

--