diff --git a/.DS_Store b/.DS_Store index 14b065f..22af7a5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..5582a60 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,76 @@ +name: Deploy Jekyll site to Pages + +on: + push: + branches: ["main"] + paths: + - "docs/**" + + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow one concurrent deployment +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + # Build job + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3.0' + bundler-cache: true + cache-version: 0 + working-directory: '${{ github.workspace }}/docs' + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v4 + + - name: Set up Jekyll + working-directory: '${{ github.workspace }}/docs' + run: | + yarn + gem install bundler + bundle install + + - name: Build with Jekyll + run: bundle exec jekyll build --config _config.yml --baseurl "${{ steps.pages.outputs.base_path }}" + working-directory: '${{ github.workspace }}/docs' + env: + JEKYLL_ENV: production + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: "docs/_site/" + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..f40fbd8 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,5 @@ +_site +.sass-cache +.jekyll-cache +.jekyll-metadata +vendor diff --git a/docs/Gemfile b/docs/Gemfile new file mode 100644 index 0000000..01e2bd8 --- /dev/null +++ b/docs/Gemfile @@ -0,0 +1,34 @@ +source "https://rubygems.org" +# Hello! This is where you manage which Jekyll version is used to run. +# When you want to use a different version, change it below, save the +# file and run `bundle install`. Run Jekyll with `bundle exec`, like so: +# +# bundle exec jekyll serve +# +# This will help ensure the proper Jekyll version is running. +# Happy Jekylling! +gem "jekyll", "~> 4.3.3" +# This is the default theme for new Jekyll sites. You may change this to anything you like. +gem "minima", "~> 2.5" +gem "just-the-docs" +# If you want to use GitHub Pages, remove the "gem "jekyll"" above and +# uncomment the line below. To upgrade, run `bundle update github-pages`. +# gem "github-pages", group: :jekyll_plugins +# If you have any plugins, put them here! +group :jekyll_plugins do + gem "jekyll-feed", "~> 0.12" +end + +# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem +# and associated library. +platforms :mingw, :x64_mingw, :mswin, :jruby do + gem "tzinfo", ">= 1", "< 3" + gem "tzinfo-data" +end + +# Performance-booster for watching directories on Windows +gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] + +# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem +# do not have a Java counterpart. +gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock new file mode 100644 index 0000000..6cb0f18 --- /dev/null +++ b/docs/Gemfile.lock @@ -0,0 +1,110 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + bigdecimal (3.1.8) + colorator (1.1.0) + concurrent-ruby (1.3.4) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86_64-darwin) + ffi (1.17.0-x86_64-linux-gnu) + forwardable-extended (2.6.0) + google-protobuf (4.28.1-arm64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.1-x86_64-darwin) + bigdecimal + rake (>= 13) + google-protobuf (4.28.1-x86_64-linux) + bigdecimal + rake (>= 13) + http_parser.rb (0.8.0) + i18n (1.14.6) + concurrent-ruby (~> 1.0) + jekyll (4.3.4) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-feed (0.17.0) + jekyll (>= 3.7, < 5.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + just-the-docs (0.10.0) + jekyll (>= 3.8.5) + jekyll-include-cache + jekyll-seo-tag (>= 2.0) + rake (>= 12.3.1) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.9.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + minima (2.5.2) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (6.0.1) + rake (13.2.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) + ffi (~> 1.0) + rexml (3.3.7) + rouge (4.3.0) + safe_yaml (1.0.5) + sass-embedded (1.78.0-arm64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86_64-darwin) + google-protobuf (~> 4.27) + sass-embedded (1.78.0-x86_64-linux-gnu) + google-protobuf (~> 4.27) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.6.0) + webrick (1.8.1) + +PLATFORMS + arm64-darwin + x86_64-darwin-20 + x86_64-linux-gnu + +DEPENDENCIES + http_parser.rb (~> 0.6.0) + jekyll (~> 4.3.3) + jekyll-feed (~> 0.12) + just-the-docs + minima (~> 2.5) + tzinfo (>= 1, < 3) + tzinfo-data + wdm (~> 0.1.1) + +BUNDLED WITH + 2.5.9 diff --git a/docs/LICENSE b/docs/LICENSE new file mode 100644 index 0000000..7d510d0 --- /dev/null +++ b/docs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 just-the-docs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..deb32a8 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,10 @@ +title: modulith +description: Modulith is a dotnet new template for Modular Monoliths. It streamlines the creation of new .Net solutions and the addition of modules to existing ones. +theme: just-the-docs +plugins: + - jekyll-feed + +url: https://github.com/ardalis/modulith.github.io + +aux_links: + Template Repository: https://github.com/ardalis/modulith/ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d7f6e62 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,191 @@ +--- +title: Home +layout: home +--- +# Ardalis.Modulith + +[![CI](https://github.com/david-acm/modulith/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/david-acm/modulith/actions/workflows/ci.yml) +[![NuGet Version](https://img.shields.io/nuget/vpre/Ardalis.Modulith)](https://www.nuget.org/packages/Ardalis.Modulith) + +* _⚠️This project is a work in progress and will likely receive many API changes before v2.0.0. Please keep this in mind when using, as there will be breaking changes often._ + +* _⚠️ Automatic project references for new modules are only supported in .Net SDK 9.0.100-preview.7.* and newer. Please make a [manual project reference](#add-a-reference-to-the-new-module) when using earlier versions._ + +**πŸ†• Try UI Module generation with Blazor. Jump to [Modules with UI](#modules-with-ui)** + +(originally hosted at **david-acm/modulith** - thanks David for the contribution!) + +Modulith is a `dotnet new template` suite for [Modular Monoliths](https://dometrain.com/bundle/from-zero-to-hero-modular-monoliths-in-dotnet/). It streamlines the creation of new .Net solutions and the addition of modules to existing ones. + +But, what is a Modular Monolith? Glad you asked. It is a software architecture style to build maintainable applications as a single unit, but in nicely separated modules (Modu-lith, pun intended πŸ™ƒ). + More [about Modular Monoliths](#-about-modular-monoliths). + +# πŸš€ Quickstart + +#### Install the tool running: + +```pwsh +dotnet new install Ardalis.Modulith +``` + +#### Create a new solution: + +``` pwsh +dotnet new modulith -n eShop --with-module Payments +``` + +`eShop` is your solution name, and `Payments` is the first module name (defaults to `FirstModule` if not specified). + +#### Create a new module + + +``` pwsh +cd eShop +dotnet new modulith --add basic-module --with-name Shipments --to eShop +``` + +*⚠️ `cd` into the solution folder to add the module inside the solution.* + +`Shipments` is the name of your new module. This will create a new module folder with the same three projects as in `Payments/`. + +#### Add a reference to the new module + +Run: + +``` pwsh +dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj +``` + +That's it, no need to register the new service -- [but you can](#direct-service-registration). The template scans you assemblies and registers services from your modules. + +Happy coding! + +Running the solution should show both modules with their default endpoint: + + + +#### Direct service registration + +However, if you prefer more control and less magic, or you want to modify registration class, you can remove the `builder.DiscoverAndRegisterModules();` in `program.cs` and add the service registration for each module: + +```cs +using eShop.Shipments +... +PaymentsModuleServiceRegistrar.ConfigureServices(builder); +``` + +# πŸ›οΈ Solution directory structure + +The previous command creates the following project structure: + +- eShop + - `Users/` πŸ‘ˆ Your first module + - `eShop.Web/` πŸ‘ˆ Your entry point + +Inside `Payments`, the second module added, you will find the project folders: + +- `eShop.Payments/` πŸ‘ˆ Your project code goes here +- `eShop.Payments.Contracts/` πŸ‘ˆ Public contracts other modules can depend on +- `eShop.Payments.Tests/` πŸ‘ˆ Your module tests + +## Project dependencies + +Since this is a Modular Monolith, there are a few rules that are enforced to guarantee the modularity: + +- Every type in `eShop.Payments/` is internal +- This is enforced by an [ArchUnit](https://github.com/TNG/ArchUnitNET) test in `eShop.Payments.Tests/` +- The only exception to the last two rules is the static class that configures the services for the module: `PaymentsModuleServiceRegistrar.cs` +- `.Contracts/` and `.Tests/` projects depend on `eShop.Payments/`. The opposite is not possible. This is by design. + +\* *You can always change these rules after you have created the solution to suit your needs. But be mindful of why you are changing the rules. For example, it is ok to add an additional public extensions class to configure the application pipeline, while adding a public contract to `eShop.Payments/` is not. We have a project for those.* + +## Adding a reference automatically to new modules + +This is only supported on .Net 9 preview 6. If you are running an earlier version you will need to run [these commands manually](#add-a-reference-to-the-new-module). + +⚠️ `cd` into the solution folder. I.e. `eShop/`, then run: + +``` pwsh +dotnet new modulith-proj --ModuleName Shipments --existingProject eShop.Web/eShop.Web.csproj +``` + +Here `Shipments` is the name of your new module, and `eShop.Web/eShop.Web.csproj` is the path to your web entry project. If you change this, make sure you update it to the new path and that is relative to the solution folder. + +# Modules with UI + +You can generate a solution with a Blazor UI by using the ```--WithUi```: + +``` pwsh +dotnet new modulith -n eShop --with-module Payments --WithUi +``` + +Running the application will show the following blazor app: + +![Screenshot of Blazor app with Payments module]() + +The app uses [MudBlazor](https://www.mudblazor.com/) as the component library. The template includes a menu item and page for the newly created module with UI whose components are defined in the ```eShop.Payments.UI``` project. We include a link to the Swagger UI page in the _API_ menu item. + +The previous command will create a solution with a few additional projects. + +- **eShop.UI:** Is the client project that will be compiled to WebAssembly and executed from the browser. This contains the layout and routes components; but most importantly the ```program.cs``` to register the services for the client side application. +- **eShop.Payments.UI:** Is a razor class library where you can define the components specific to that UI module. +- **eShop.Payments.HttpModels** Contains the DTOs used to send requests from the Blazor client project (```eShop.UI```) to the WebApi endpoints in ```eShop.Shipments```. + +## Adding new modules with UI + +New modules with UI can be added running: + +```pwsh +cd eShop +dotnet new modulith --add basic-module --with-name Shipments --to eShop --WithUi +``` + +⚠️ New modules with UI can only be added to solutions that were instantiated using the ```-WithUI``` parameter. + +However, to allow routing to the newly created module component for the ```Shipments``` module, you need to register the new assembly. + +In blazor WebAssembly, the routeable components that are not present in the executing assembly need to be passed as arguments to the ```Router``` component. In this template, this is done using the ```BlazorAssemblyDiscoveryService```. Simply add the following to the ```GetAssemblies``` array: + +```cs +typeof(ShipmentsComponent).Assembly +``` + +After the modification the class should look like this: + +```cs +public class BlazorAssemblyDiscoveryService : IBlazorAssemblyDiscoveryService +{ + public IEnumerable GetAssemblies() => [typeof(PaymentsComponent).Assembly, typeof(ShipmentsComponent).Assembly]; +} +``` + +For each additional module you create you will need to add a new assembly to this array. + +More about this in [Blazor's documentation page: Lazy Load Assemblies with WebAssembly](https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-lazy-load-assemblies?view=aspnetcore-8.0#assemblies-that-include-routable-components) + +# πŸ“Š About Modular Monoliths + +A Modular Monolithic app benefits from the simple deployment of a monolith and the separation of concerns that microservices offer. While avoiding the complexities and maintainability issues they can introduce. When you are ready and *if* you need it, you can split a module as a microservice. Best of both worlds 🌎 + +This is not a new concept. Martin Fowler [explains it here](https://martinfowler.com/bliki/MonolithFirst.html), and Ardalis teaches it [here](https://ardalis.com/introducing-modular-monoliths-goldilocks-architecture/#:~:text=A%20Modular%20Monolith%20is%20a%20software%20architecture%20that,that%20they%20are%20loosely%20coupled%20and%20highly%20cohesive.). + +The templates in this project follow the solution structure as taught by [Ardalis](https://github.com/ardalis) in [his course *Modular Monoliths in DotNet*](https://dometrain.com/bundle/from-zero-to-hero-modular-monoliths-in-dotnet/). + +# πŸ›ƒ Custom templates + +No template fits all needs. If you want to customize the template you can [clone this repository as a template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template). + +Once you have cloned the repo locally you can make your custom changes in the `working/content` directory. This directory contains the project templates used for every instatiation. Then you can install the template locally running: + +*⚠️ Make sure to uninstall the original template* +```pwsh +dotnet new install . +``` + +You can find more information about building ```dotnet new``` templates, including how to add commands and parameters, at Microsoft docs page: [Custom templates for dotnet new](https://learn.microsoft.com/en-us/dotnet/core/tools/custom-templates) + +# πŸ—‘οΈ Uninstall Modulith + +```pwsh +dotnet new uninstall Ardalis.Modulith +```