A Soft Attempt at Writing a System Design Paper (Spot-A-Hub API)

·

13 min read

"Simplicity is the soul of efficiency."

Austin Freeman (in The Eye of Osiris)

The purpose of this project is to design a simple and free API that allows users (remote workers, developers, designers and so on) to find hubs or co-working spaces close to them. Be it by name, tag and state or physically accessible places in Nigeria (fun fact, even Google does not have this much data), also if users cannot find a hub on the list, they can decide to add more hubs to help others find them easily. This article will provide a detailed system design for the Spot-A-Hub API project.

We will use .NET Core Web API as the technology stack, Heroku Postgres as the database of choice, and deploy the application to Heroku. We will also discuss very briefly how the API can be implemented in mobile, desktop, and web applications.

Before we proceed, let me give you a little perspective as to why I started this project.

The Motivation Behind This Project

I think it is nice to note that this project was my main motivation behind taking coding seriously, I started using Twitter actively in 2019 and I joined a few programming communities on WhatsApp as well (GDG - Google Developer Group, Ikorodu) and someone pointed out that they couldn't find a co-working space close to their area (I think the person mentioned areas around Ikorodu, Yaba, and even Ikeja). At the time, I only knew of NestHub and CCHub, both of which I knew had offices at Yaba and Ikeja.

Still, there were no co-working spaces or hubs at Ikorodu. Not long after that, I saw a tweet from another developer on Twitter saying they couldn’t find a hub close to their area. At this point, I became curious because even though I wanted to find a co-working space close to my area where I could be surrounded by people of like minds (developers), I stayed at Ketu, Ikosi Lagos.

Well it was quite difficult at the time, I saw the problem and I thought hey, why not build this as a project and make it open to people who would like to use it in their next pet project or possibly their next big thing?

Side Note: I built the web-based version with a lot more features and I will be writing "a soft attempt at writing a system design paper (Spot-a-hub web edition) in my forthcoming article.

The source code for this project is available here

Let’s get started, this article is divided into:

  1. System architecture process

  2. Database design

  3. Design Pattern choice

  4. Project Architecture

  5. API Implementation

  6. Testing

  7. Conclusion

System Architecture Process

To design the system architecture for the Spot-A-Hub API project, we will follow and consider these 7 steps:

  1. Identifying the requirements: For this project, the requirements are to allow users to search for a hub by name, tag, and state. The system should be able to handle a large number of requests and respond quickly with accurate information.

  2. Selecting the Technology stack: Once we have identified the requirements of this system, we can go ahead to choose the technology stack. For this project we have chosen .Net Core as the tech stack because it provides a robust and scaleable framework for building and deploying web APIs, to learn more about .Net, visit here

  3. Design the database: The next step in designing the system architecture is to design the database. For this project, we are going with Heroku Postgres as the database of choice because it provides a scaleable and reliable database system that can handle a large number of requests, to learn more about Heroku Postgres please kindly visit here

  4. Designing the API: Once we have designed the database, we can start planning the API. We need to create the endpoints for searching hubs by name, tag, and state. We also need to define the data that will be returned by the API such as; the name, website URL, longitude and latitude for map integration, and location of the hub.

  5. Implement the API: The next step is to implement the API. We need to create the controllers and routes for the API endpoints. We also need to define the models for the data that will be returned by the API.

  6. Test the API: Once the API is implemented, we need to test it to ensure that it is working as expected. We can use tools like Postman to test the API endpoints and ensure that they are returning the correct data and deal with errors if any arise while testing.

  7. Deploy the API: The final step in designing the system architecture is to deploy the API. We will deploy the API to Heroku and ensure that it is available to users around the world.

Note: if you want to deploy your next .Net core MVC application or web API project using Heroku and Heroku Postgres as the database, I wrote a helpful article that can be used for both MVC and Web API projects here

By following this system architecture process as a template, we can build a robust and scaleable system for the Spot-A-Hub API project (and pretty much any project) that meets the requirements of the users and provides them with easy access to information about co-working spaces or hubs.

Database Design

For the Spot-A-Hub API project, we have chosen Heroku Postgres as the database of choice. Heroku Postgres (coined from PostgreSQL) is a powerful and open-source relational database system that provides a scaleable and reliable solution for storing and retrieving data.

Below is a simple database schema diagram of the Hubb Model:

We will design the database using the Hubb model, this model will store information about each co-working space or hub. The properties of the Hubb model are:

public class Hubb
    {
        public Guid HubbId { get; set; }
        public string Name { get; set; }
        public string State { get; set; }
        public string Address { get; set; }
        public string Website { get; set; }
        public string Tags { get; set; }
        public string Image { get; set; }
        public string Longitude { get; set; }
        public string Latitude { get; set; }
    }

But when we run a migration to Heroku Postgres, the datatypes will change from "Guid -> Uuid" and "string -> text":

 modelBuilder.Entity("Entities.Models.Hubb", b =>
                {
                    b.Property<Guid>("HubbId")
                        .ValueGeneratedOnAdd()
                        .HasColumnType("uuid");

                    b.Property<string>("Address")
                        .HasColumnType("text");

                    b.Property<string>("Image")
                        .HasColumnType("text");

                    b.Property<string>("Latitude")
                        .HasColumnType("text");

                    b.Property<string>("Longitude")
                        .HasColumnType("text");

                    b.Property<string>("Name")
                        .HasColumnType("text");

                    b.Property<string>("State")
                        .HasColumnType("text");

                    b.Property<string>("Tags")
                        .HasColumnType("text");

                    b.Property<string>("Website")
                        .HasColumnType("text");

                    b.HasKey("HubbId");

                    b.ToTable("Hubbs");
                });

Next, let us take a look at the choices that were made for Design patterns and Architecture. First off, let's talk about the Design Pattern choice.

Design Pattern Choice

The design pattern used in this project is the Unit of Work Repository pattern, some might argue that this is a simple application and we do not need so much complexity, so why not stick to the regular Repository pattern by following the KISS (Keep It Simple, Stupid) principle and using the Non-Generic Repository pattern instead.

Non-generic Repository vs Unit of Work Repository

The Repository is a class defined for an entity or model with all the possible database operations/transactions. For example, a repository for a Hubb entity will have the basic CRUD (Create, Read, Update, Delete) operations and other business logic (e.g FindHubByLocation, FindHubByLatitude and so on) related to the Hubb entity.

The Non-generic repository allows us to use one repository class for each model or entity. For example, if we add another entity “Reviews”, Reviews and Hubb will have their separate repositories like “HubbRepository and ReviewsRepository” so all database operations related to Hubb and Reviews entities will be in their respective repositories.

The Unit of work repository on the other hand allows us to group one or more database CRUD operations into a single transaction.

Limitation of the Non-generic repository vs Unit of work repository

At first sight, it would make much sense to use the non-generic repository pattern because this way, we can reduce complexity, but I thought of scaleability, and this influenced my decision to switch to the "unit of work" repository pattern.

The non-generic repository pattern can become less maintainable and less scaleable as the application grows due to the fixed set of methods for each entity type and potential code duplication.

On the other hand, the Unit of Work pattern provides better scaleability and maintainability as it provides a central point for managing database operations and ensures transactional consistency. Making it easier to manage database connections, transactions, and error handling.

Next, let us look at how this project is structured.

Project Architecture

For this project, I used clean architecture, there is no one way to use clean architecture in your project. I have worked with different codebases and everyone’s definition of clean architecture is different, but the underlying rule of clean architecture is the separation of responsibilities into different layers (business logic, presentation layer, domain layer).

I separated my projects into:

  • Spot-a-hub (presentation layer)

  • Entities (domain layer)

  • Repository (business logic)

  • Extensions (inversion of control container)

Let us do a breakdown of each project:

Spot-a-hub: this serves as the presentation layer; it holds our controller and swagger, which allows us to visualize our API and its endpoints.

Entities: This serves as the domain layer, where we write everything related to our database contexts, models, migrations and DTOs (data transfer objects).

Repository: this serves as the business logic layer which contains a folder called Contracts (or we can call this our interfaces, IHubbRepository, IRepositoryBase, IRepositoryManager). While outside of it we have the implementations (HubbRepository, RepositoryBase, RepositoryManager).

  • RepositoryBase: This is where the CRUD database operations which have been defined via IRepositoryBase are implemented. Click here to view the source code.

  • RepositoryManager: This is where the Business Logic Repositories are implemented and managed, click here to view the source code.

  • HubbRepository: This is where our business logic methods are implemented, click here to view the source code.

Contracts: This is where we define the CRUD database operations, business logic interfaces and their methods

  • IRepositoryBase: This is where the CRUD database operations are defined (Create, FindAll, FindByCondition, Update, Delete), click here to view the source code

  • IRepositoryManager: This is where the Business logic Repositories (such as IHubbRepository and others will be defined and managed) and saveChanges (which will be used to save transactions to the database) are defined, click here to view the source code

  • IHubbRepository: This is where our business logic methods are defined, click here to view the source code

Extensions: Our services are created here instead of putting them all in our Startup.cs class (.net 5.0 is the version that was used to build this project at the time, but it still very much works with .net 6.0, and even 7.0. The only difference with 6.0 and 7.0 is that we do not have the Startup.cs class anymore so we will have to call these classes into Program.cs) because as we start to have more repositories and other services, our startup.cs class will begin to look dirty and maintainability will be a problem. click here to view.

Next up, let us talk about our API Implementation and details.

API Implementation

Now that we have designed the database, we can start implementing the API. As discussed, We will use .NET Core API to implement the API endpoints and connect to the Heroku Postgres database.

API Endpoints

We will define the following API endpoints for the spot-a-hub API:

GET '/api/gethubs'

  • This endpoint will return a list of all the hubs.

GET '/api/getbyname?name={name}'

  • This endpoint will return a list of hubs that match the given name.

GET '/api/getbytag?tag={tag}'

  • This endpoint will return a list of hubs that match the given tag.

GET '/api/getbystate?state={state}'

  • This endpoint will return a list of hubs that match the given state.

POST '/api/addhub'

  • This endpoint is used to add new hubs

API Models

We will define the Hub, this model represents a co-working space or hub.

The properties of the Hub model are:

  • Id (uuid): The unique identifier of the hub.

  • Name (string): The name of the hub.

  • State (string): The state where the hub is located.

  • Address (string): The address of the hub.

  • Tags (string): The tag(s) associated with the hub.

  • Image (string): The image of the hub.

  • Longitude (string): The longitude of the hub.

  • Latitude (string): The latitude of the hub.

API Implementation Details

We will use Entity Framework Core to connect to the Heroku Postgres database and retrieve data. Entity Framework Core is an open-source ORM (Object-Relational Mapping) that provides a simple way to access data from a database, to learn more about EF CORE please click here

We will create an AppDbContext class that will inherit from DbContext. This class will represent the database context and will provide methods for querying the database.

 public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {

        }
        public DbSet<Hubb> Hubbs { get; set; }
    }

We will also create a HubbRepository class that will provide methods for retrieving hubs from the database.

We will then create the API controllers for the endpoints defined above. The controllers will use the IRepositoryManager interface to retrieve data from the database and return the data in the appropriate format.

Let us look at a code snippet that best explains this:

 public class HubController : ControllerBase
    {
        private readonly IRepositoryManager _repositoryManager;
        public HubController(IRepositoryManager repositoryManager)
        {
            _repositoryManager = repositoryManager;
        }

        [HttpGet]
        [Route("/api/gethubs")]
        public ActionResult GetAllHubs()
        {
          var getHubs = _repositoryManager.Hubb.GetHubs(trackChanges: false);
        return Ok(gethubs);
        }
}

Mobile, Desktop, and Web Application Implementation

The Spot-A-Hub API can be implemented in mobile, desktop, and web applications, we can use various programming languages and frameworks. Here are some examples:

Mobile: For mobile applications, we can use Java or Kotlin for Android applications and Swift for iOS applications, for hybrid applications, we can make use of Xamarin, .Net MAUI, Flutter, React Native and many more.

Desktop: For desktop applications, we can use C# and the .NET Framework to build WinForms and WPF(Windows Presentation Foundation). We can use the HttpClient class to make HTTP requests to the API and retrieve data.

Web: For web applications, we can use JavaScript and frameworks like Angular, Vue or React to build the front end of the application. We can use the Axios library to make HTTP requests to the API and retrieve data.

Below is a simple flow of how the clients (web, mobile and desktop) communicate with the API and Database:

Testing

We will be testing the endpoints via postman to check for errors and see if the API is working as intended.

Let us test each endpoint and see how it is returned in postman

  1. Getting all hubs

  2. Getting hub(s) by tag

    The image below shows us a scenario where the hub is found:

The image below shows us a scenario where the hub is not found:

  1. Getting hub(s) by state

    The image below shows us a scenario where the hub is found:

The image below shows us a scenario where the hub is found:

  1. Searching for the hub(s) by name

    The image below shows us a scenario where the hub is found:

The image below shows us a scenario where the hub is found:

By using the Spot-A-Hub API in mobile, desktop, and web applications, we can provide users with easy access to information about co-working spaces or hubs. Users can search for hubs by name, tag, and state, and retrieve information about each hub, such as its name, website, address, and location.

Conclusion

In this article, we have provided a detailed system design for the Spot-A-Hub API project. We use .NET Core Web API as the technology stack, Heroku Postgres as the database of choice, and we chose Heroku as the most suitable cloud platform for deployment. We discussed about the database design, design pattern choice and project architecture breakdown. We also discussed how the API can be implemented in mobile, desktop, and web applications. By following this system architecture process, we can build a robust and scaleable system that meets the requirements of the users and provides them with easy access to information about co-working spaces or hubs.

The source code for this project is available here

Please if you have questions, observations, feedback, or comments, kindly drop them in the comments section, thanks.

References

Unit of work in repository pattern with an example: Unit Of Work in Repository Pattern - Dot Net Tutorials

Implementing the repository and unit of work patterns: https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

The Clean architecture:

The Clean Architecture — Beginner’s Guide | by Bharath | Better Programming and Why use a Clean Architecture: parts, principles and advantages (mytaskpanel.com)