Skip to content

Commit

Permalink
Merge pull request #31 from ardalis/add-ui-projects
Browse files Browse the repository at this point in the history
Add UI project templates
  • Loading branch information
ardalis authored Aug 20, 2024
2 parents 55e858c + b9b7fc6 commit f077ec8
Show file tree
Hide file tree
Showing 120 changed files with 1,446 additions and 1,110 deletions.
Binary file added .DS_Store
Binary file not shown.
22 changes: 12 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@ jobs:
matrix:
template:
- working-dir: modulith
project: Modulith.sln
- working-dir: modulith-proj
project: NewModule/Modulith.NewModule
- working-dir: modulith-proj
project: NewModule/Modulith.NewModule.Contracts
- working-dir: modulith-proj
project: NewModule/Modulith.NewModule.Tests
project: TestModulith.sln

name: 'Build project source: ${{ matrix.template.working-dir }}'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -65,13 +59,21 @@ jobs:
working-directory: test
run: dotnet new modulith -n eShop --with-module Payments

- name: Instance project
- name: Instance basic project
working-directory: test/eShop
run: dotnet new modulith-proj --add-module Shipments --to eShop
run: dotnet new modulith --add basic-module --with-name Shipments --to eShop

- name: Add project reference
working-directory: test/eShop
run: dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj

- name: Instance ddd project
working-directory: test/eShop
run: dotnet new modulith --add ddd-module --with-name Billing --to eShop

- name: Add project reference
working-directory: test/eShop
run: dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj

- name: Restore
working-directory: test/eShop
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/qodana_code_quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:
qodana:
if: ${{ false }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 'Qodana Scan'
uses: JetBrains/[email protected]
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
80 changes: 66 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[![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 v1.0.0 please keep this in mind when using, there will be breaking changes often.**
**⚠️This project is a work in progress and will likely receive many API changes before v1.0.0. Please keep this in mind when using, as there will be breaking changes often.**

**🆕 Try UI Module generation with Blazor. Jump to [UI Modules](#ui-module)**

(originally hosted at **david-acm/modulith** - thanks David for the contribution!)

Expand All @@ -10,9 +12,9 @@ Modulith is a `dotnet new template` suite for [Modular Monoliths](https://dometr
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).

# 🏁 Start here
# 🚀 Quickstart

#### Install by running:
#### Install the tool running:

```pwsh
dotnet new install Ardalis.Modulith
Expand Down Expand Up @@ -56,15 +58,14 @@ 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, remove the `builder.DiscoverAndRegisterModules();` in `program.cs` and add the service registration for each module:
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:
Expand All @@ -73,7 +74,7 @@ The previous command creates the following project structure:
- `Users/` 👈 Your first module
- `eShop.Web/` 👈 Your entry point

Inside `Payments`, you will find the project folders:
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
Expand All @@ -84,36 +85,87 @@ Inside `Payments`, you will find the project folders:
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 test in `eShop.Payments.Tests/`
- The only exception to the last two rules is the static class that configures the services for the module: `UsersModule...Extensions.cs`
- `.Contracts/` and `.Tets/` projects depend on `eShop.Payments/`. The opposite is not possible. This is by design.
- 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

We support this, but the .Net SDK does not yet. There is an active PR at [dotnet/sdk #40133](https://github.com/dotnet/sdk/pull/40133). Give it a vote if you'd like this feature:
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 changed this, make sure you update it to the new path and that is relative to the solution folder.
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](<with-ui.png>)

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<Assembly> 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 maitainability 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 🌎
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 weant to customize the template you can change it in the `working/content` directory and running:
No template fits all needs. If you want to customize the template you can change it in the `working/content` directory and running:

*⚠️ Make sure to uninstall the original template*
```pwsh
Expand Down
19 changes: 19 additions & 0 deletions Test-UiWithDotnetCli.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cd /Users/davidchaparro/RiderProjects/modulith/test/
rm -rf /Users/davidchaparro/RiderProjects/modulith/test/**
dotnet new uninstall /Users/davidchaparro/RiderProjects/modulith/working/content/modulith
dotnet new install /Users/davidchaparro/RiderProjects/modulith/working/content/modulith

dotnet new modulith -n eShop --with-module Payments --WithUi

cd eShop
dotnet new modulith --add basic-module --with-name Shipments --to eShop --WithUi
dotnet new modulith --add ddd-module --with-name Billing --to eShop

dotnet build

dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj
dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj

dotnet build

dotnet run --project ./eShop.Web/eShop.Web.csproj
12 changes: 11 additions & 1 deletion test.pwsh → Test-WithDotnetCli.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ cd /Users/davidchaparro/RiderProjects/modulith/test/
rm -rf /Users/davidchaparro/RiderProjects/modulith/test/**
dotnet new uninstall /Users/davidchaparro/RiderProjects/modulith/working/content/modulith
dotnet new install /Users/davidchaparro/RiderProjects/modulith/working/content/modulith
dotnet new modulith -n eShop --with-module Payments

dotnet new modulith -n eShop --with-module Payments

cd eShop
dotnet new modulith --add basic-module --with-name Shipments --to eShop
dotnet new modulith --add ddd-module --with-name Billing --to eShop

dotnet build

dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj
dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj

dotnet build

dotnet run --project ./eShop.Web/eShop.Web.csproj
31 changes: 31 additions & 0 deletions qodana.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"

#Specify IDE code to run analysis without container (Applied in CI/CD pipeline)
ide: QDNET

#Specify inspection profile for code analysis
profile:
name: qodana.starter

dotnet:
solution: working/content/modulith/TestModulith.sln
#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>

#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>

#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh

#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
2 changes: 1 addition & 1 deletion test
Submodule test updated from ad068e to c00c0b
Binary file added with-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added working/.DS_Store
Binary file not shown.
70 changes: 35 additions & 35 deletions working/Ardalis.Modulith.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,40 @@
<PackageTags>dotnet;.NET;dotnet new;dotnet cli;Visual Studio;template;project template;c#;modular;monolith;modular monolith</PackageTags>
<PackageProjectUrl>https://github.com/ardalis/modulith</PackageProjectUrl>

<!-- Keep package type as 'Template' to show the package as a template package on nuget.org and make your template available in dotnet new search.-->
<PackageType>Template</PackageType>
<TargetFramework>net8.0</TargetFramework>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<NoDefaultExcludes>true</NoDefaultExcludes>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup>
<LocalizeTemplates>false</LocalizeTemplates>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TemplateEngine.Tasks" Version="*" PrivateAssets="all" IsImplicitlyDefined="true" />
<PackageReference Include="MinVer" Version="5.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Content Include="content\**\*" Exclude="content\**\bin\**;content\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<!-- Keep package type as 'Template' to show the package as a template package on nuget.org and make your template available in dotnet new search.-->
<PackageType>Template</PackageType>
<TargetFramework>net8.0</TargetFramework>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<NoDefaultExcludes>true</NoDefaultExcludes>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup>
<LocalizeTemplates>false</LocalizeTemplates>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.TemplateEngine.Tasks" Version="*" PrivateAssets="all" IsImplicitlyDefined="true"/>
<PackageReference Include="MinVer" Version="5.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Content Include="content\**\*" Exclude="content\**\bin\**;content\**\obj\**"/>
<Compile Remove="**\*"/>
</ItemGroup>

<ItemGroup>
<None Include="README.md" Pack="true" PackagePath=""/>
</ItemGroup>

</Project>
Loading

0 comments on commit f077ec8

Please sign in to comment.