Orchard Core and Service Fabric - Part 3 - Creating the BooksCatalog Service Fabric Service

Creating the BooksCatalog Service

Creating a Service Fabric Service is straightforward with the help of Visual Studio. We will keep the service simple so we can focus on how we would call a SF service from a custom Orchard module.

To create a new Service Fabric service, right-click on the Services node of the BooksUnlimited SF project, then click Add -> New Service Fabric Project.

The screen that appears next should look familiar, since we saw this screen when we first created the Service Fabric project and the initial Stateless ASP.NET Core service. This time, we will go with the Stateless Service project template and call it BooksUnlimited.BooksCatalog.

When Visual Studio is done creating the project, the first thing we will want to do is clean up the BooksCatalog class, which represents our "micro" service. Primarily, we want to get rid of the RunAsync method. The result should look like this:

/// <summary>
/// An instance of this class is created for each service instance by the Service Fabric runtime.
/// </summary>
internal sealed class BooksCatalog : StatelessService
{
public BooksCatalog(StatelessServiceContext context)
: base(context)
{ }

/// <summary>
/// Optional override to create listeners (e.g., TCP, HTTP) for this service replica to handle client or user requests.
/// </summary>
/// <returns>A collection of listeners.</returns>
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[0];
}
}

Great! So now we have a microservice. But it doesn't do anything. How do we make it return a list of books to callers? Let's tackle that next

Service Fabric Remoting

In order for consumers to communicate with a Service Fabric service, that service needs to provide one or more ServiceInstanceListener objects. A listener can be a variety of things, such as:

  • A TCP/IP listener that listens on a socket for requests.
  • An HTTP listener that listens for HTTP requests
  • A service bus listener that listens for message on a queue.
  • Etc.

In fact, when we look at the BooksUnlimited.Web SF service (the Web class), we'll see that it returns a KestrelCommunicationListener:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[]
{
new ServiceInstanceListener(serviceContext =>
new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
{
ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting Kestrel on {url}");

return new WebHostBuilder()
.UseKestrel()
.ConfigureServices(
services => services
.AddSingleton<StatelessServiceContext>(serviceContext))
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
.UseUrls(url)
.Build();
}))
};
}

In order for our BooksUnlimited.Web application to be able to consume our BooksUnlimited.BooksCatalog service, we could consider exposing a RESTful Web API using ASP.NET Core using a KestrelCommunicationListener as well. However, let's assume that we don't want to expose the service to the outside world, but instead, only allow the web application to consume the service.

To achieve that, we need to return an IServiceRemotingListener. We can create an actual instance of such a listener by invoking an extension method called CreateServiceRemotingListener that is provided as part of the Microsoft.ServiceFabric.Services.Remoting package, which you should add now.

Before we can use the CreateServiceRemotingListener extension method however, we need to create a C# interface that derives from IService (provided by the Remoting package as well). We will call this interface IBooksCatalogService, and will be used by consumers to communicate with the service. Since the IBooksCatalogService interface is to be used by consumrs, we need to create a shared class library that consumers can reference. Because the last thing we want is for our consumers (such as our BooksUnlimited.Web application) to have to reference the SF service project directly.

So let's go ahead and create a new class library project called BooksUnlimited.BooksCatalog.Abstractions (make sure this is a Class Library project targeting .NET Framework 4.7 - remember, Service Fabric does not yet fully support .NET Standard 2.0 at the time of this writing).

Next, add the following interface class to the class library:

public interface IBooksCatalogService : IService
{
Task<IList<Book>> GetBooksAsync();
}

Make sure to add the Microsoft.ServiceFabric.Services.Remoting package to this project as well, since we need to use the IService interface.

Notice that the GetBooksAsync method returns a Task, which is required by the Service Fabric Remoting API.

The Book class should look something like this:

public class Book
{
public string Title { get; set; }
public string Genre { get; set; }
public string Author { get; set; }
public string Summary { get; set; }
}

Next, we need to reference the BooksUnlimited.BooksCatalog.Abstractions class library from our BooksUnlimited.BooksCatalog project so that our service can implement the interface. To implement the interface, simply have the BooksCatalog service class implement the interface and its single GetBooksAsync() method as follows:

public Task<IList<Book>> GetBooksAsync()
{
var books = new[]
{
new Book { Title = "The Martian", Author = "Andy Weir", Genre = "Science Fiction", Summary = "When astronauts blast off from the planet Mars, they leave behind Mark Watney, presumed dead after a fierce storm. With only a meager amount of supplies, the stranded visitor must utilize his wits and spirit to find a way to survive on the hostile planet. Meanwhile, back on Earth, members of NASA and a team of international scientists work tirelessly to bring him home, while his crew mates hatch their own plan for a daring rescue mission."},
new Book { Title = "We Are Legion (We Are Bob)", Author = "Dennis E. Taylor", Genre = "Science Fiction", Summary = "Bob Johansson has just sold his software company and is looking forward to a life of leisure. There are places to go, books to read, and movies to watch. So it's a little unfair when he gets himself killed crossing the street. Bob wakes up a century later to find that corpsicles have been declared to be without rights, and he is now the property of the state. He has been uploaded into computer hardware and is slated to be the controlling AI in an interstellar probe looking for habitable planets. The stakes are high: no less than the first claim to entire worlds.If he declines the honor, he'll be switched off, and they'll try again with someone else. If he accepts, he becomes a prime target.There are at least three other countries trying to get their own probes launched first, and they play dirty. The safest place for Bob is in space, heading away from Earth at top speed. Or so he thinks.Because the universe is full of nasties, and trespassers make them mad - very mad." },
new Book { Title = "Our Mathematical Universe", Author = "Max Tegmark", Genre = "Non Fiction", Summary = "Max Tegmark's doorstopper of a book takes aim at three great puzzles: how large is reality? What is everything made of? Why is our universe the way it is? Tegmark, a professor of physics at MIT, writes at the cutting edge of cosmology and quantum theory in friendly and relaxed prose, full of entertaining anecdotes and down-to-earth analogies. His book seeks to induct the reader into a wildly speculative cosmic vision of infinite time and space and infinite parallel universes. Close to his heart is an extreme Pythagorean/Platonic thesis: physical reality is ultimately nothing other than a giant mathematical totality." },
new Book { Title = "Life 3.0", Author = "Max Tegmark", Genre = "Non Fiction", Summary = "How will Artificial Intelligence affect crime, war, justice, jobs, society and our very sense of being human? The rise of AI has the potential to transform our future more than any other technology--and there's nobody better qualified or situated to explore that future than Max Tegmark, an MIT professor who's helped mainstream research on how to keep AI beneficial." }
};

return Task.FromResult((IList<Book>)books);
}

And now that our service class implements IService (via IBooksCatalogService), we can leverage the CreateServiceRemotingListener extension method as follows:

protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(context => this.CreateServiceRemotingListener(context))
};
}

Before we continue, let's change the names of our two services to something more pleasingly. Open ApplicationManifest.xml in the BooksUnlimited SF project, and look for the <DefaultServices> element. This is where all of the SF services are listed that are to be deployed as part of the Servie Fabric application.

Change the <Service> elements as follows:

<Service Name="Web" ServicePackageActivationMode="ExclusiveProcess">
...
</Service>
<Service Name="BooksCatalog" ServicePackageActivationMode="ExclusiveProcess">
...
</Service>

And that's it! With all this in place, we are now ready to start implementing a consumer that can make calls to this microservice, which shows up nicely in our SF cluster when deployed:

In the next and final part, we will create a custom Orchard module that consumes the service we just created. See you there!

Leave a comment