GithubHelp home page GithubHelp logo

davidajohn / fotostoriomicroservices Goto Github PK

View Code? Open in Web Editor NEW
22.0 1.0 4.0 1.93 MB

.NET application built using a microservice architecture with Docker containers. Includes a Blazor WebAssembly e-commerce store with Stripe Elements payment integration.

C# 67.11% Dockerfile 1.44% HTML 28.33% CSS 2.00% JavaScript 0.50% TSQL 0.62%
dotnet docker blazor blazor-webassembly blazor-server tailwindcss stripe pci-dss jsinterop microservices

fotostoriomicroservices's Introduction

Foto Storio Microservices

Foto Storio Microservices is a .NET e-commerce application built with a microservice architecture using Docker containers.

A 'monolithic' version of the application can be found here: https://github.com/DavidAJohn/FotoStorio


Screenshot

Features

  • Blazor WebAssembly e-commerce store with a responsive layout created using Tailwind CSS 3
  • Basket functionality using a Redis database
  • PCI DSS-compliant payment integration using Stripe Elements and JSInterop
  • Authentication and authorisation using .NET Core Identity
  • API Gateways for the client applications using YARP and Ocelot
  • Discount pricing database using gRPC
  • Async messaging using RabbitMQ and MassTransit
  • Inventory management using PostgreSQL and async messaging
  • Centralised, structured logging using Serilog and Seq

Getting Started

To run the application locally, make sure you have Docker Desktop installed and running on your system.

After downloading or cloning the repository, you'll need to rename the '.env.sample' file in the 'fotostorio-microservices' folder to '.env' and update the placeholder values with your own values.

Once the .env file configuration is complete, open a terminal inside the application's source folder ('fotostorio-microservices') and run the following command:

docker-compose --profile clientapps up -d

This may take a few minutes, depending on whether or not you already have some or all of the Docker images downloaded.

The application features a SQL Server with four databases which involves a 1.4Gb download just for the image itself.

With all application services running, Docker will need at least 4Gb RAM, preferably 6-8Gb if you can spare it.

Once all of the containers are up and running, visit http://localhost:8000 in your browser to view the store.

The Blazor Server Admin site will be available at http://localhost:8020.

The Health Checks/Application Status site will be available at http://localhost:8100/hc-ui.

The Seq logging site will be available at http://localhost:8200.

Application Architecture

The following diagram illustrates the structure of the application and gives an overview of how each of these microservices interact.

Screenshot

On the left-hand side we have the Blazor client applications which provide the user interfaces for our customers and admins to interact with the application. These client applications in turn send requests to a particular gateway which is configured to communicate with a sub-set of the backend services in order to retrieve the data or perform the functions requested by its client application. These services communicate with each other by sending and receiving asynchronous messages to and from the Event Bus.

Development Goals

The main aim of this application is to offer an example of how an ecommerce application could be split up using a microservice architecture, with each service running in its own Docker container. It is NOT intended to be a production-ready, turnkey enterprise solution that can immediately be deployed to Azure or AWS.

Primarily, my intention was to explore this type of architecture and to develop my knowledge of the different types of interaction required between the different elements. As an experienced .NET developer, I was familiar with many of the more common parts of the application such as Web API, Blazor, SQL Server and Entity Framework, but less familiar with others like minimal APIs, gRPC, Ocelot and async messaging with RabbitMq and Mass Transit. I've learned a great deal about the latter topics and almost as much about the former ones and how they need to be adapted to function well within this type of architecture.

Inevitably, there are structural similarities with other microservice applications that you may be familiar with, such as Microsoft’s eShopOnContainers reference application, although this is not a fork and there is no code taken directly from Microsoft’s application.

FotoStorio offers a significant divergence from Microsoft’s method of dealing with ordering and payment processing. Given the justifiably onerous burden that the payment card industry places on anyone handling card details, it seems unwise to promote storing those details locally, even within a reference application. Instead of doing this, FotoStorio uses Stripe to process card payments, therefore avoiding any issues with PCI DSS compliance.

Another key intention was to follow best practice as closely as possible, unless it imposed a significant barrier to entry, particularly for developers who may not have a great deal of experience using Docker or setting up SSL certificates, for example. One particular area where this will be apparent to experienced developers is in the use of ASP.NET Identity rather than an implementation of IdentityServer. This doesn't provide the typical security functions like OAuth2 or OpenIdConnect, but is simpler to set up and understand. This was a concession for the sake of developer convenience.

Emphasis has been placed on client applications built using single page application frameworks like Microsoft’s Blazor framework rather than MVC. The main store is built using standalone Blazor WebAssembly, whereas site admin functionality is provided by a separate Blazor Server app. The client applications have been styled using Tailwind CSS. While it may still be a controversial choice with some developers it has quickly become my preferred choice of CSS framework, particularly when building responsive layouts, where it becomes a huge time-saver. I can't remember another piece of development technology that moved so quickly from repulsive to near-essential in my estimations.

Most of the microservices also have sample data seeded into them when they first run, using a code-first approach with Entity Framework. This was to allow a developer to immediately have a functional store without having to add products manually. The images used throughout the application are stored at ImgBB.com.

fotostoriomicroservices's People

Contributors

davidajohn avatar dependabot[bot] avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

fotostoriomicroservices's Issues

Extending Serilog and Seq to all projects

Although Serilog is now being used to log to a Seq instance from the vital backend services like the Products API and the Ordering API, having done that it feels like the entire application should probably be doing the same as well.

Seq's UI is fantastic for filtering logs by application/service (among many other things), so although we may not need the full power of Serilog's structured logging of objects for the other services, there is still a lot to be said for having all logs available in one place, especially for an application with this kind of microservice architecture.

Ordering API returning 404 to admin orders page

While updating the Admin.BlazorServer UI project, I noticed the admin page which shows customer orders provoked a 404 from the Ordering API (via the admin gateway) because there are no orders in the database (or none in the last x days).

I'm not sure this is the correct response when there are no orders to return, but there can be huge debates over this.

Ultimately, what does the CustomerOrders.razor page need to do? Is there value in returning a different response when there are no orders to return, as opposed to when there is a problem retrieving the orders?

Health Checks failing & missing icons

There are a couple of unrelated issues with the Application Status project which uses the built-in .NET health checks generated by each project:

  1. There is a cosmetic issue where a particular font isn't being loaded after the update to .NET 8. It appears to be a relatively simple fix involving the webpack config. There is an open issue containing a PR waiting for the contributor to agree to the CLA. I'll leave it for now as this isn't vital. It's just to show the icons in the UI.
  2. The projects that have been updated to .NET 8 are all showing as being down (when they are not). Is it related to the default port changing to 8080 from 80? Microsoft's docs on Health Checks are here

Integration Tests using SQL Server and EF

After quite a bit of experimentation, I've hit a brick wall trying to write integration tests that run against a real instance of SQL Server (or at least, a 'real' SQL Server instance created using Testcontainers from inside a WebApplicationFactory).

Whatever I try, I always end up back in the same situation where I get an error from Entity Framework about the entity already being tracked. I've tried just about every suggestion I've found on Stack Overflow and elsewhere, and the outcome always seems to be the same. There comes a point where everyone stops trying to get this to work and looks at an alternative involving an In Memory database and/or SQLite.

For reference, here's an example from Microsoft's own sample projects.

I'm not particularly keen on that approach because you're not testing against the actual database and there are issues with LINQ.

Microsoft have an absolutely insane suggestion which involves running tests inside a transaction and then rolling it back afterwards, to avoid any commits being made to the database. That seems like a tacit admission that there is no way around these EF tracking issues. It also just relies on an existing database, rather than having some kind of automatic creation and teardown.

Here's a potential alternative which uses the Docker.DotNet library:

.NET Core Integration Tests using a Sql Server Database in Docker

Exception Middleware

There are several Services projects that have controllers which contain try...catch logic. It makes me feel a little uneasy seeing 500 errors being returned in so many of the catch sections.

I'd like to get rid of that repetitive code and use some form of exception middleware instead, which would make the controllers simpler and centralise exception handling and logging.

Something like Jason Watmore's solution would be ideal: https://jasonwatmore.com/post/2022/01/17/net-6-global-error-handler-tutorial-with-example

Adding an Ordering/Marketing Aggregator?

While planning the features for a simple client application for a fictional Marketing department, I'm seeing scenarios where there will be a need to combine data from orders, discounts and marketing campaigns.

I'm considering whether there would be value in adding another API project that aggregates that information.

For example: if you wanted to see whether a marketing campaign generated additional sales, you would want to see if the number of orders increased during that period and how many of the orders placed during that campaign included discounted products.

But will there be a sufficient number of instances where we need that combined data? Alternatively, if we simply get data from different sources and combine them inside a client app, is it complex and/or slow to accomplish the functionality that we want?

Let's take the example I mentioned above - if we have a client application with a campaigns page that can show orders made during a campaign, we can simply have an order service that gets orders within the campaign's start and end dates. That is not sufficiently complex to justify the additional aggregator project.

Are there potentially more complex scenarios that would justify the additional work though? What if there was a requirement for more complex data analysis, like breaking down sales by brands or models?

I don't have a definitive answer to that question at the moment, and the decision will probably be influenced mostly based on my experience while adding features to this marketing client app.

In a real life scenario, I would probably wait to see how the requirements of the marketing department staff evolved and only if they reached a level of complexity that made client app development onerous would I look at adding an aggregator API. Or if we could demonstrably see bottlenecks when combining data within the app.

Stripe API updates

The Blazor store project (Store.BlazorWasm) currently uses v39 of the Stripe SDK. The latest version is v45.

However, each version of the SDK is tied to a particular version of Stripe's API, which can be selected in the Stripe dashboard. You have a 72 hour window in which you can revert back to your previous API version if you update it.

In order to move to v40> of the SDK, I would need to update the API as well. There are quite a few breaking changes that have been introduced and while on the face of it there don't appear to be any changes that affect FotoStorio, it's always better to be safe than sorry with something like this.

This came to light because v39 of the Stripe SDK installs a version of Newtonsoft.Json that is now considered vulnerable (v12.0.3).

I may just update Newtonsoft.Json itself to a non-vulnerable version for the time being, then I'll look at updating the Stripe SDK and API at a later time.

Migrate gateways from Ocelot to YARP?

It's no secret that Ocelot is no longer being actively developed and Tom Pallister himself has suggested people use alternatives like YARP, although he has said he will update Ocelot with each new version of .NET.

FotoStorio has several gateway projects that currently use Ocelot for routing requests to different services on the back end. They all function without any problems, but YARP is probably a better long-term option given that it is actively maintained by Microsoft and used internally by them.

I'll try creating a gateway using YARP instead of Ocelot to see how the amount of work involved stacks up against the benefits moving forward. I would imagine there may be speed and memory usage benefits to using YARP, as well as the obvious future-proofing.

Marketing client application

It was always my intention to create a simple client application that enabled the creation of marketing campaigns and discounts.

There is a marketing gateway already built and the backend functionality is available in both the Discount minimal API project and the Discount gRPC project.

It really only needs to give a fictional marketing dept the ability to create a campaign with start and end dates which includes a selectable list of discounted products.

I could build something using Blazor, but I'd always wanted to demonstrate how to add something different to the mix, so the feasible options for me are:

Blazor (Wasm or Server)
Angular
Next.Js
Vue/Nuxt

Product details and Inventory

The store's product details page currently has a hard-coded limit of 5 items.

The idea of allowing more than one to be bought is obviously a little contrived for a store that sells high-cost goods like cameras. Plus, why would anybody buy two of the same camera/lens?

But, given that we have an inventory API with current stock for each product, it would be good to link this up and set the limit to however many are in stock.

Use of AutoMapper in Discount Grpc

While updating the version of AutoMapper used in parts of the application (in order to switch to the non-DI v13> package), it occurred to me that perhaps the Discount Grpc project should be using a custom mapper class instead - which should be quicker (but slightly less convenient).

Otherwise, we're potentially losing part of the speed advantage that Grpc offers.

Breaking changes in .NET 8 Docker images

There are some breaking changes that have been introduced with the .NET 8 Docker images.

While testing, I found that several services that had been updated to .NET 8 where no longer responding, although no errors where visible in the logs. I quickly discovered that I'd missed a memo about the default port changing from 80 to 8080.

There are some other changes as well, which Andrew Lock has explained excellently, as ever:

https://andrewlock.net/exploring-the-dotnet-8-preview-updates-to-docker-images-in-dotnet-8/

Also here's a practical example of a .NET 8 app that uses docker compose:

https://www.yogihosting.com/docker-compose-aspnet-core/

Update to .NET 8

It's time to gradually update the application to .NET 8, even if it's just a case of switching to the new v8 libraries for now and implementing new features later.

Anything that can help improve speed and reduce the memory footprint of each microservice has to be a good thing.

Serilog and Seq

An addition that I'd like to make to the application is to add Serilog to at least some of the services - definitely to the Ordering API and the Products API at least.

This would probably only need a few additional nuget packages and perhaps some minor alterations to the logging statements. Sinks for the console and Seq would likely be sufficient.

I'll then add a Seq container instance to the application based on the official Docker image.

Time to consider .NET 7

It's probably time to start thinking about which projects would benefit from being upadted to .NET 7.

Anything involving Entity Framework should be stringly considered, given the performance gains.

Also the Discount minimal API would be a good candidate.

Basket API and AutoMapper

The Basket API also uses AutoMapper when updating (or POSTing to) a customer basket.

It uses a call to the Discount Grpc service to retrieve any current discounts for a particular product when it is added to a basket.

Testing in Postman, I found that with 2 items in a basket the request was completing in anywhere from 125ms to 288ms.

It would be interesting to see how much could be shaved off that by using what would be a very simple, one-way custom mapper class instead of AutoMapper.

Originally posted by @DavidAJohn in #23 (comment)

Update all projects to .NET 6

At the moment, the application consists of a mixture of .NET 5 and .NET 6 projects.

The Blazor client apps are .NET 6, but most of the backend services are still .NET 5.

It's not a huge problem, but it would be nice to have everything consist, especially as .NET 6 is the LTS release.

The first step would be updating the version numbers in the .csproj files and testing that each project still builds successfully.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.