Repository: jhipster/jhipster-lite
Pull request #10711 (2024-08-31 15:56:54)

Front hexagonal architecture documentation module
For frontends refactoring, the idea will be to have this module as dependency. This way, we avoid adding lots of example code in the generated applications.

What do you think of this idea?


Patch:
diff --git a/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/application/FrontHexagonalArchitectureDocumentationApplicationService.java b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/application/FrontHexagonalArchitectureDocumentationApplicationService.java
new file mode 100644
index 000000000..90cea775a
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/application/FrontHexagonalArchitectureDocumentationApplicationService.java
@@ -0,0 +1,16 @@
+package tech.jhipster.lite.generator.client.hexagonal_documentation.application;
+
+import org.springframework.stereotype.Service;
+import tech.jhipster.lite.generator.client.hexagonal_documentation.domain.FrontHexagonalDocumentationModuleFactory;
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+
+@Service
+public class FrontHexagonalArchitectureDocumentationApplicationService {
+
+  private static final FrontHexagonalDocumentationModuleFactory factory = new FrontHexagonalDocumentationModuleFactory();
+
+  public JHipsterModule buildModule(JHipsterModuleProperties properties) {
+    return factory.buildModule(properties);
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactory.java b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactory.java
new file mode 100644
index 000000000..1f1cca4bc
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactory.java
@@ -0,0 +1,18 @@
+package tech.jhipster.lite.generator.client.hexagonal_documentation.domain;
+
+import static tech.jhipster.lite.module.domain.JHipsterModule.*;
+
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.file.JHipsterSource;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+
+public class FrontHexagonalDocumentationModuleFactory {
+
+  public static final JHipsterSource SOURCE = from("client/common/hexagonal-documentation");
+
+  public JHipsterModule buildModule(JHipsterModuleProperties properties) {
+    return moduleBuilder(properties)
+      .documentation(documentationTitle("Front hexagonal architecture"), SOURCE.template("front-hexagonal-architecture.md"))
+      .build();
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/infrastructure/primary/FrontHexagonalDocumentationModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/infrastructure/primary/FrontHexagonalDocumentationModuleConfiguration.java
new file mode 100644
index 000000000..9da5e0c10
--- /dev/null
+++ b/src/main/java/tech/jhipster/lite/generator/client/hexagonal_documentation/infrastructure/primary/FrontHexagonalDocumentationModuleConfiguration.java
@@ -0,0 +1,22 @@
+package tech.jhipster.lite.generator.client.hexagonal_documentation.infrastructure.primary;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+import tech.jhipster.lite.generator.client.hexagonal_documentation.application.FrontHexagonalArchitectureDocumentationApplicationService;
+import tech.jhipster.lite.generator.slug.domain.JHLiteModuleSlug;
+import tech.jhipster.lite.module.domain.resource.JHipsterModuleResource;
+
+@Component
+class FrontHexagonalDocumentationModuleConfiguration {
+
+  @Bean
+  JHipsterModuleResource frontHexagonalDocumentationModule(FrontHexagonalArchitectureDocumentationApplicationService documentation) {
+    return JHipsterModuleResource.builder()
+      .slug(JHLiteModuleSlug.FRONT_HEXAGONAL_ARCHITECTURE)
+      .withoutProperties()
+      .apiDoc("Frontend", "Add front hexagonal architecture documentation")
+      .standalone()
+      .tags("client", "documentation")
+      .factory(documentation::buildModule);
+  }
+}
diff --git a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
index b33ecd337..c8e21fef7 100644
--- a/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
+++ b/src/main/java/tech/jhipster/lite/generator/slug/domain/JHLiteModuleSlug.java
@@ -33,6 +33,7 @@ public enum JHLiteModuleSlug implements JHipsterModuleSlugFactory {
   FLYWAY_MYSQL("flyway-mysql"),
   FLYWAY_POSTGRESQL("flyway-postgresql"),
   FRONTEND_MAVEN_PLUGIN("frontend-maven-plugin"),
+  FRONT_HEXAGONAL_ARCHITECTURE("front-hexagonal-architecture"),
   NODE_GRADLE_PLUGIN("node-gradle-plugin"),
   GATEWAY("gateway"),
   GIT_INFORMATION("git-information"),
diff --git a/src/main/resources/generator/client/common/hexagonal-documentation/front-hexagonal-architecture.md.mustache b/src/main/resources/generator/client/common/hexagonal-documentation/front-hexagonal-architecture.md.mustache
new file mode 100644
index 000000000..15f0a044e
--- /dev/null
+++ b/src/main/resources/generator/client/common/hexagonal-documentation/front-hexagonal-architecture.md.mustache
@@ -0,0 +1,140 @@
+# Hexagonal architecture in front
+
+There are many ways to implement Hexagonal Architecture in front applications. The way described here is, by no means,
+THE WAY. It is just a way, feel free to adapt it to your needs.
+
+This documentation is not an introduction to hexagonal architecture. If you want to learn more about it, you can read
+[this page](https://github.com/jhipster/jhipster-lite/blob/main/documentation/hexagonal-architecture.md).
+
+## Folder structure
+
+in `src/main/webapp/app` root folders represents the bounded contexts. When starting something new, you won't know what
+Bounded Context you're in. Don't spend too much time on that: create a folder, and you'll rename / move it later.
+
+Let's dive into a Bounded Context (example in vue.js, but it's the same for other frontends frameworks):
+
+```bash
+├── brewery
+│   ├── application
+│   │   ├── BreweryKeys.ts
+│   │   ├── BreweryProvider.ts
+│   │   └── BreweryRouter.ts
+│   ├── domain
+│   │   ├── BreweryName.ts
+│   │   ├── BreweryRepository.ts
+│   │   ├── Brewery.ts
+│   │   └── BreweryType.ts
+│   └── infrastructure
+│       ├── primary
+│           ├── BreweryVue.vue
+│       └── secondary
+│           ├── RestBreweryRepository.ts
+│           └── RestBrewery.ts
+
+```
+
+In `application` we have the wiring points in the application, we'll come back to that later.
+In `domain` we have the Domain Model, it's the heart of the application, the code that really matters. In the frontend,
+the Domain Model represents the current state of the application.
+In `infrastructure/primary` we basically find the UI, it's the code to display elements to the user.
+In `infrastructure/secondary` we find the code used to communicate other elements of the solution (call the backend,
+sending messages, etc.).
+
+Some Bounded Contexts are shared between multiple Bounded Contexts, in that case, they are in `src/main/webapp/app/shared` and follow the
+same structure.
+
+## Handling injections
+
+Hexagonal Architecture is about dependencies inversion. For that, you'll need Dependency Injection (DI).
+
+There are many ways to do this, we like using [piqure](https://github.com/Gnuk/piqure). It's a 43 line library allowing
+typesafe injections.
+
+For each element you want to inject, you'll need a key:
+
+```typescript
+import { key } from 'piqure';
+import { BreweryRepository } from '@/home/domain/BreweryRepository.ts';
+
+export const BREWERY_REPOSITORY = key<BreweryRepository>('BreweryRepository');
+```
+
+You'll then be able to provide an implementation:
+
+```typescript
+import { AxiosHttp } from '@/shared/rest/infrastructure/secondary/AxiosHttp.ts';
+import { BREWERY_REPOSITORY } from '@/home/application/HomeKeys.ts';
+import { provide } from '@/injections.ts';
+import { RestBreweryRepository } from '@/home/infrastructure/secondary/RestBreweryRepository.ts';
+
+export const provideForHome = (rest: AxiosHttp): void => {
+  provide(BREWERY_REPOSITORY, new RestBreweryRepository(rest));
+};
+```
+
+Here our `BreweryRepository` (and interface in the domain) is implemented by the `RestBreweryRepository` in the
+secondary part of the application.
+
+To actually make injections, you'll need to call `provideForHome` in your `main.ts`.
+
+Once you've done that, you can inject dependencies:
+
+```typescript
+import { inject } from '@/injections.ts';
+import { BREWERY_REPOSITORY } from '@/home/application/HomeKeys.ts';
+
+export default {
+  name: 'BreweryVue',
+  setup() {
+    const breweries = inject(BREWERY_REPOSITORY);
+
+    //TODO: component logic
+
+    return {
+      // stuff
+    };
+  },
+};
+```
+
+On the frontend, we are injecting ports (in this case, the `BreweryRepository`) in the primary adapters. It is not the
+standard way to do it, but it's a totally valid tradeoff: reducing glue and still decoupling infrastructure and domain.
+
+## Handling routes
+
+Bounded Context will have their routes. One way to provide them is to create a `XXXRoutes.ts` file in the `application`:
+
+```typescript
+import type { RouteRecordRaw } from 'vue-router';
+import HomepageVue from '@/home/infrastructure/primary/HomepageVue.vue';
+
+export const homeRoutes = (): RouteRecordRaw[] => [
+  {
+    path: '/',
+    redirect: { name: 'Homepage' },
+  },
+  {
+    path: '/home',
+    name: 'Homepage',
+    component: HomepageVue,
+  },
+];
+```
+
+(example for vue.js, need some adaptation for other frontends frameworks)
+
+You can then append this routes to the main routes in `router.ts`:
+
+```typescript
+import { createRouter, createWebHistory } from 'vue-router';
+import { homeRoutes } from '@/home/application/HomeRouter';
+
+const routes = [...homeRoutes()];
+
+const router = createRouter({
+  history: createWebHistory(),
+  routes,
+});
+
+export default router;
+```


Test patch:
diff --git a/src/test/features/client/front-hexagonal-architecture-documentation.feature b/src/test/features/client/front-hexagonal-architecture-documentation.feature
new file mode 100644
index 000000000..1870b22fe
--- /dev/null
+++ b/src/test/features/client/front-hexagonal-architecture-documentation.feature
@@ -0,0 +1,7 @@
+Feature: Front Hexagonal Architecture documentation
+
+  Scenario: Should apply front hexagonal architecture documentation
+    When I apply modules to default project
+      | front-hexagonal-architecture |
+    Then I should have files in "documentation"
+      | front-hexagonal-architecture.md |
diff --git a/src/test/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactoryTest.java b/src/test/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactoryTest.java
new file mode 100644
index 000000000..689740b10
--- /dev/null
+++ b/src/test/java/tech/jhipster/lite/generator/client/hexagonal_documentation/domain/FrontHexagonalDocumentationModuleFactoryTest.java
@@ -0,0 +1,25 @@
+package tech.jhipster.lite.generator.client.hexagonal_documentation.domain;
+
+import static tech.jhipster.lite.module.domain.JHipsterModulesFixture.*;
+import static tech.jhipster.lite.module.infrastructure.secondary.JHipsterModulesAssertions.*;
+
+import org.junit.jupiter.api.Test;
+import tech.jhipster.lite.TestFileUtils;
+import tech.jhipster.lite.UnitTest;
+import tech.jhipster.lite.module.domain.JHipsterModule;
+import tech.jhipster.lite.module.domain.properties.JHipsterModuleProperties;
+
+@UnitTest
+class FrontHexagonalDocumentationModuleFactoryTest {
+
+  private static final FrontHexagonalDocumentationModuleFactory factory = new FrontHexagonalDocumentationModuleFactory();
+
+  @Test
+  void shouldBuildModule() {
+    JHipsterModuleProperties properties = propertiesBuilder(TestFileUtils.tmpDirForTest()).build();
+
+    JHipsterModule module = factory.buildModule(properties);
+
+    assertThatModule(module).hasFile("documentation/front-hexagonal-architecture.md");
+  }
+}
diff --git a/tests-ci/generate.sh b/tests-ci/generate.sh
index 89298cd29..da6db64b4 100755
--- a/tests-ci/generate.sh
+++ b/tests-ci/generate.sh
@@ -155,7 +155,8 @@ elif [[ $application == 'fullapp' ]]; then
     "jqassistant-jmolecules" \
     "jqassistant-spring" \
     "license-apache" \
-    "renovate"
+    "renovate" \
+    "front-hexagonal-architecture"
 
   cucumber_with_jwt