Shopfoo is a full-stack web app. It is a demo project showcasing the Safe Clean Architecture—a term I coined.
😉 The name Shopfoo is a reference to the chop suey dish and the song by System of a Down.
The solution is based on the SAFEr.Template. It's written in F# on both Client and Server sides:
- Client:
- Fable 4 F#-to-JavaScript transpiler
- SPA: React 19 under the hood
- HTML DSL: Feliz 2.9
- ELM architecture: Elmish
- MVU loop per page using
React.useElmishfrom Feliz.UseElmish FullContextobject stored in the root view and shared to page views
- MVU loop per page using
- Design system: Feliz.DaisyUI built on 🌼DaisyUI and tailwindcss
- Build: ⚡Vite.js (instead of webpack)
- Routing: navigation between pages using Feliz.Router
- Server:
- ASP.NET Core
- 🦒Giraffe as a functional overlay
- Client-Server:
- Fable.Remoting supporting the "Remoting API", with endpoints grouped between
HomeandProduct - Shared
ApiErrortype, hiding theErrordomain type - Custom helpers for the calls to the Remoting API:
- Types:
ApiResult<'a> = Result<'a, ApiError>,ApiCall<'a> = Start | Done of ApiResult<'a> - Objects:
fullContext.PrepareRequest(...) : Cmder→cmder.ofApiRequest(ApiRequestArgs) : Cmd<Msg>, abstracting the ElmishCmd.OfAsync.either
- Types:
- Translations:
- Grouped by pages, loaded on demand and cached on the Client side
- Friendly and strongly-typed syntax for the views: e.g.
translations.Home.Theme.Garden,translations.Product.Discount discount.Value
- Fable.Remoting supporting the "Remoting API", with endpoints grouped between
☝️ Disclaimer: The architecture is strongly opinionated and is not a silver-bullet! Of course, you can apply it completely on your projects—as it is fairly comprehensive for code organization, although it is not production ready. You can also just pick some ideas and techniques.
The solution is fully written in F#, a multi-paradigm language. The architecture embraces both the functional programming (FP)—the core design of F#—and the object-oriented programming, in a balanced manner.
The architecture combines—or rather is inspired by—many documented architectures:
- Clean architecture: domain-centric, multi-layer: Presentation|Infrastructure → Application → Domain
- Hexagonal architecture: domain-centric—the hexagon contains the Application and Domain layers—the Presentation layer is at its left—it's the driving part—the Infrastructure is at its right—the driven part.
- Modular monolith: multi-domain solution
- Screaming architecture: the domain is expressed directly through the file/folder organization
- Vertical slice architecture: gathering by features rather than by technical aspects
- SAFE architecture: Client-Shared-Server 3-project solution, F# full-stack
Hence its name, ❝ Safe Clean Architecture. ❞
Run the relevant command(s) in a console at the root of the solution:
# Both Client and Server
dotnet run
# Server only (fast: no dotnet restore)
dotnet run Server
# Client only (fast: no dotnet restore)
yarn start:fast
# Client only, with dotnet restore
yarn startThen, you can browse the application at the URL: http://localhost:8080.
Shopfoo is the back-office tool to manage a shop selling products, limited to 5 well-known technical books for simplicity's sake.
💡 Inspired by Task based UI, a YouTube video by Derek Comartin.
Several domains are involved: Catalog, Sales, Purchases, Warehouse.
- Catalog Info
- Index
- Display the table of products
- Display ellipsis for texts longer than 2 lines
- Handle two categories: Books and Goodies
- Filter by categories, book author
- New component "Filter"
- Filter by categories, book author
- Add new product
- Delete product (if no stock, no sales, no purchases)
- Display the table of products
- Details
- Edit the image, with preview.
- Edit the product name: required, max 128 chars.
- Edit the product description: max 512 chars.
- Edit the book authors
- Select/Unselect existing author(s)
- New component "MultiSelect"
- Add new author
- Select/Unselect existing author(s)
- Index
- Sales
- List price (a.k.a. recommended price)
- Display the price fetched from the server
- Action: Define if not defined
- Action: Modify if defined (intent: Increase or Decrease)
- Action: Remove if defined
- Retail price
- Display the price fetched from the server
- Display discount / list price if any
- Display margin / Purchased price (last or average over 1Y)
- Action: Modify (intent: Increase or Decrease)
- Action: Mark as sold out
- Sales
- Display last sale
- Display 1Y sale: quantity, total
- Action: Input sales
- List price (a.k.a. recommended price)
- Purchasing
- Purchased price
- Display last price and average over 1Y based on the purchases and stock adjustment, with an arrow indicating if it has increased
↗️ or decreased↘️ - Action: Receive purchased products
- Display last price and average over 1Y based on the purchases and stock adjustment, with an arrow indicating if it has increased
- Purchased price
- Warehouse
- Stock
- Display the stock based on the stock events
- Action: Inventory adjustment
- Stock
- Navigation bar with a breadcrumb composed of clickable segments to go up any level
- User rights:
- Log in using a persona to demonstrate the access rights
- Client side: on access denied → hide button or section, use readonly inputs, redirect to an authorized page: login or page not found
- Server side: exception → not user-friendly but just to prevent hacking
- Localization: instant switch from English to French
- Theme: 4 light themes and 4 dark themes, with colors preview
Moneytype to handle prices with different currency (DollarsandEuros)- Currency symbol position in the input box:
$ 22.99vs22.99 €
- Currency symbol position in the input box:
- Form validation
- Deploy to Azure
- Install Iconify and FontAwesome 6 Solid icons via Glutinum
- Use FontAwesome icons
- Architecture tests
- Workflow tests
- UI tests