Project Coqfoss: Vue in een Maven project

Welkom bij weer een nieuwe blog over Coqfoss, ons interne project waarin we nieuwe dingen te leren door samen te werken. In deze reeks blogs nemen we je mee in de technieken die we gebruiken en delen we de ervaringen die we opdoen. In deze blogpost gaan we een voorkant toevoegen aan ons project. 

Een van de primaire doelen van project Coqfoss is te leren over ons vakgebied door samen te werken. Als het om front-end technologieën gaat, werken de meeste collega’s in hun project met Angular en een enkeling heeft ook React. Een andere grote speler in frontend land is Vue. Coqfoss lijkt ons een uitgesproken kans om eens meer met dit framework te doen. 

Vue bestaat sinds 2014 en is bedacht door ex-Google werknemer Evan You. Anders dan React en Angular wordt Vue niet onderhouden door grote bedrijven. In plaats daarvan is er een Patreon waardoor Vue doorontwikkeld blijft worden. Ondertussen heeft Vue op Github meer sterren dan React en is het daar het ‘populairste’ frontend framework. Vanwege de Chinese achtergrond van Evan You heeft Vue ook een grote gebruikersbase in China. Bekende voorbeelden van grote bedrijven die Vue gebruiken zijn onder andere Gitlab en Alibaba.

Eerste stappen

Een eerste stap om Vue te proberen zetten we door Vue toe te voegen aan een simpele HTML pagina. In de resources folder van onze web module zetten we een index.html neer. Door Spring wordt deze default geserveerd als hoofdpagina.

In de HTML nemen we in de <head> een verwijzing op naar Vue. In dit geval gebruiken we de versie die voor development en prototyping gebruikt wordt. Er bestaat ook een productievariant.

<head>
  <title>Coqfoss Employee Dashboard</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
</head>

We kunnen dan vervolgens in een script blok lager in de pagina de Vue app instantiëren. We zorgen er voor dat de app in een div op basis van een identifier geladen wordt. We maken ook een variabele voor een naam die we in dit simpele voorbeeld tonen. Met de dubbele accolades kunnen we vervolgens de variabele in de html gebruiiken.

...
<div id="app">
   <h1>Hello {{name}}!</h1>
 </div>

 <script> new Vue({ 
     el: '#app',
     data() {    
        return { name: "you" }   
     } 
})</script>
...

Praten met de backend

We willen ook graag data uit onze backend halen om ook daadwerkelijk boeken te tonen. Dat doen we in twee stappen. Eerst voegen we een created() functie aan de Vue app toe. Die zorgt er voor dat als de app laadt de data wordt binnengetrokken. Vervolgens moeten we er voor zorgen dat we dat resultaat weer in een nieuwe variabele in onze data() functie opslaan. We kunnen de default fetch gebruiken om een HTTP call te doen. Het /library endpoint op onze backend bestaat ook al, en geeft een lijstje boeken terug, met elk een title en owner. Al met al ziet dat er als volgt uit:

...
<script> new Vue({
  el: '#app',
  data() {
    return {
      books: []
    }
  },
  created() {
    fetch("/library")
      .then(response => {
        return response.json()
      }).then(books => {
      this.books = books;
    })
  }
})</script>
...

Nu we de data hebben kunnen we met een simpele html lijst de data tonen. Met de v-for directive zorgen we er voor dat we over de lijst van boeken heen kunnen itereren. De hier getoonde title en owner van het boek komen gewoon zo uit de backend terug. Omdat het hier om Javascript gaat, gaat dat dan vanzelf goed.

<div id="app">
  <h1>Library currently contains the following books:</h1>
  <ul>
    <li v-for="book in books">
       <b>title:</b> {{book.title}} <b>owner:</b> {{book.owner}}
    </li>
  </ul>
</div>

In de index.html kunnen we zo al met weinig werk een complete Vue pagina aan de buitenwereld tonen.

Op naar een permanentere oplossing!

Alhoewel dit voorbeeld natuurlijk leuk is voor de simpele dingen, is het voor het neerzetten van een compleet medewerkersdashboard niet helemaal geschikt. We willen namelijk graag de boel kunnen testen, zodat we ook in de frontend TDD kunnen werken. Ook zou het fijn zijn als we functionaliteiten en componenten van elkaar kunnen scheiden zodat we volgens vast kunnen houden aan het Single Responsability Principle. Genoeg reden dus om het voorbeeld te extraheren naar een complete frontend module.

We beginnen onze reis door de Vue CLI te installeren. Met een enkel commando maken we een nieuw project : vue create lecoqfoss-front. We moeten dan een keuze maken tussen Vue 2 en Vue 3 om het project te maken. We kiezen voor nu voor Vue 2, alhoewel Vue 3 sinds september 2020 beschikbaar is.

Als we met tree naar de structuur kijken ziet die er zo uit:

 ├── babel.config.js
 ├── package.json
 ├── public
 │   └── index.html
 ├── README.md
 └── src
     ├── App.vue
     ├── assets
     │   └── ...
     ├── components
     │   └── HelloWorld.vue
     └── main.js

Het liefst zouden we zien dat ook dit nieuwe Vue projecte uiteindelijk meegeserveerd wordt met onze applicatie. Op die manier houden we voorlopig een artifact en deployment. Daardoor blijft onze CI/CD pipeline ook hetzelfde, terwijl we voor de toekomst wel de optie openhouden om het alsnog allemaal apart te doen. Dat bespaart ons op de korte termijn veel werk.

Wrappen van de frontend

Om dit voor elkaar te krijgen gaan we de frontend in een aparte Maven module stoppen binnen het Coqfoss project. Net als voor alle andere modules doen we dat door in de root pom de nieuwe module op te nemen, die we frontend noemen. In de root pom van ons project ziet dat er zo uit:

<packaging>pom</packaging>   
<modules>     
     <module>frontend</module>
     <module>web</module>
     ...
</modules>
<groupId>com.codesquad.lecoqfoss</groupId>
<artifactId>root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>root</name> 

Vervolgens zorgen we er voor dat we een pom.xml toevoegen aan de net gemaakte frontend module. We zorgen er voor dat de frontend folder verder binnen het coqfoss project staat. In de pom.xml nemen we verder de root pom van het project op als parent, en geven we in een build plugin aan dat we een npm run build willen doen als er een mvn compile lifecycle phase langst komt. In zijn totaliteit ziet dat er dan zo uit:

  <parent>
    <artifactId>root</artifactId>
    <groupId>com.codesquad.lecoqfoss</groupId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>frontend</artifactId>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.0.0</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>exec</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <executable>npm</executable>
          <workingDirectory>${project.basedir}</workingDirectory>
          <arguments>
            <argument>run</argument>
            <argument>build</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>

Het resultaat van de plugin is dat er na een mvn compile een /dist folder in de root van de module komt, waarin we de HTML pagina en resources kunnen vinden die we willen gaan serveren.

Leeglepelen van de /dist

De volgende stap is dan zorgen dat we het resultaat van deze build ook serveren vanuit onze applicatie. Dat doen we door in de pom.xml van onze web module, waar onze Spring applicatie leeft, nog een trucje toe te passen. Met de Maven resources plugin kopieren we de /dist folder van de frontend module naar de public folder in het artifact van de web module. Spring serveert namelijk automatisch alle statische resources die hij daar kan vinden. De configuratie van de plugin ziet er zo uit:

 <plugin>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
      <execution>
        <id>copy-resources</id>
        <phase>validate</phase>
        <goals>
          <goal>copy-resources</goal>We willen bereiken
        </goals>
        <configuration>
          <outputDirectory>target/classes/public</outputDirectory>
          <resources>
            <resource>
              <directory>${project.parent.basedir}/frontend/dist/</directory>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>

Als we na een mvn install van het project dan de Spring applicatie runnen, zien we de pagina ook geserveerd worden.

Nu we een goede opzet hebben voor de frontend, kunnen we in de volgende blog kijken naar meer features van Azure; het maken van een Cosmos Database voor het opslaan van data en het gebruik van de Key Vault voor secret management. Tot dan!

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *