In this project we developed a web based DApp NFTs Marketplace, in which the transactions are done through crypto currencies Ethereum, Bitcoin, etc..., and the user.
- Authenticates through an Ethereum wallet.
- Can sell NFTs.
- Proceed with NFTs Minting.
The Marketplace based on hybrid architecture a NoSQL database MongoDB to stock general information whereas NFTs transactions are managed by Smart Contracts.
Tools : Spring Boot, Microservices, Angular, MongoDB, Solidity , Ether.js, Hardhat, MetaMask, Devops : CI/CD, Docker, Github, Jenkins.
To effectively convey the dynamic nature of our application, we created a general class diagram. This diagram represents the abstraction of the application's functionality, allowing for a better understanding of the various interactions between classes. To organize NFTs, you can group them into collections and then categorize each collection. This allows you to interact with either individual NFTs or entire collections.
To provide all the dependencies, spring uses the pom.xml
file, which allows a better management of these independences and helps to simplify the process of configuring and setting up a Spring-based application.
So this is the pom.xml
file of the microservice-parent:
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/>
</parent>
<groupId>ma.fstt</groupId>
<artifactId>microservices-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>category-service</module>
<module>collection-service</module>
<module>transaction-service</module>
<module>user-service</module>
<module>image-service</module>
<module>service-registry</module>
<module>cloud-gateway</module>
<module>cart-service</module>
<module>nft-service</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>2022.0.0</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
So our services uses several dependencies to function properly. The first dependency, spring-boot-starter-data-mongodb, allows for the integration of MongoDB as the application's database.
The second dependency, spring-boot-starter-web, enables the application to handle HTTP requests and responses.
The third dependency, spring-cloud-starter-netflix-eureka-server, is used to manage service registration and discovery manager.
The fourth dependency, lombok, is a library that is used to minimize or remove the boilerplate code. It saves time and effort. Just by using the annotations, we can save space and readability of the source code.
The last dependency, spring-boot-starter-test, is used for testing the application.
Our application is based on a microservices architecture which is an architectural style in which a large application is built as a collection of small and independent services that communicate with each other through lightweight APIs. This approach can be beneficial in many ways, such as:
Scalability : Microservices can be scaled independently, which allows for more efficient use of resources.
Flexibility : Because services are independent, it's easier to make changes or updates to a specific part of the application without affecting the whole system.
Resilience : When a service fails, it doesn't bring the whole application down. Instead, the other services can continue to function.
Ease of deployment: Microservices can be deployed and updated independently, which can make the deployment process faster and less risky.
<parent>
<artifactId>microservices-parent</artifactId>
<groupId>ma.fstt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>name-service</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
We did implement a service registry, which is a database of services, their instances and their locations. Service instances are registered with the service registry on startup and deregistered on shutdown. So the pom.xml
of our serivce-registry contains the following dependencies:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
A cloud gateway is designed to act as a single-entry point for defined back-end APIs and microservices (which can be both internal and external). This is the pom.xml
file of our cloud-gateway:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
And this is the application.yml
file to specify our services and their paths:
server:
port: 9191
spring:
application:
name: CLOUD-GATEWAY
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
routes:
- id: TRANSATION-SERVICE
uri: lb://TRANSATION-SERVICE
predicates:
- Path=/api/transactions/**
- id: CART-SERVICE
uri: lb://CART-SERVICE
predicates:
- Path=/api/cart/**
- id: NFT-SERVICE
uri: lb://NFT-SERVICE
predicates:
- Path=/api/nfts/**
- id: USER-SERVICE
uri: lb://USER-SERVICE
predicates:
- Path=/api/users/**
- id: CATEGORY-SERVICE
uri: lb://CATEGORY-SERVICE
predicates:
- Path=/api/categories/**
- id: COLLECTION-SERVICE
uri: lb://COLLECTION-SERVICE
predicates:
- Path=/api/collections/**
- id: IMAGE-SERVICE
uri: lb://IMAGE-SERVICE
predicates:
- Path=/api/image/**
As a tool for Ethereum development, Hardhat provides a number of features that make it easier to test, deploy and develop smart contracts on the Ethereum network. One of the main advantages of using Hardhat is its local development network feature. This allows developers to test their smart contracts on a local network, without incurring real-world gas costs or making transactions on the main Ethereum network.
One of the key features of the local development network is the ability to use predefined accounts with a balance of 10000 ETH each. This allows developers to easily test their contracts without worrying about funding the accounts or paying for gas costs.
The deployment of our smart contract was completed successfully using the Hardhat manager. For each deployment, we have included information on the deployer and the contracts, including the gas price, the deployer's balance, the deployment price, and the contract address.
In the home page of our application you can just explore NFTs already created, connect your wallet, create collection, create NFT or buy one.
To authenticate in our marketplace you have to use your wallet
When you authenticate with your wallet for the first time a new unnamed user will be created
So if you want to give your account a name or change the profile picture you can click the ellipsis button and tap settings.
And when you finish click submit changes.
As you see the update applied on the database as well.
To create a collection you have to click the link in the navbar create and choose collection.
The collection created successfully.
And also a new document of the collection has created in our NoSQL database.
The user need to confirm the transaction before send it to the Blockchain.
Our NFT is created successfully.
The transaction is added to the blockchain, and the hardhat tool provides information about it, which allows us to know the NFT created from whom to whom, and the gas used.
After you create your NFT, always there is the possibility to edit it, often to decrease the price, it allows for flexibility in the buying and selling process for both the creator and potential buyers. This can help to boost the liquidity of the marketplace.
When exploring the marketplace, users can choose a collection and view its details, such as the NFTs it contains. They can also view information about the creator of the collection, as well as the total number of NFTs in the collection.
When a user chooses a NFT, he can better view it by examining the details and characteristics of the digital item. If interested, he can also choose to add it to its shopping cart for later purchase. This allows the user to review different options before making a purchasing decision.
When the user clicks on the cart icon, a new screen will appear displaying a list of all the items that have been added to the cart plus a button for deleting items. The items are listed with their name, quantity, and price, making it easy for the user to review their selections. Additionally, the user can also modify the quantity of an item or remove it from the cart if they no longer wish to purchase it. This feature is designed to provide a more convenient and user-friendly shopping experience for our customers
To create an image in docker and run it as a container we add this Dockerfile
file in our application:
# First stage
FROM node:latest as build
RUN mkdir -p /app
WORKDIR /app
COPY package.json /app/
RUN npm install
COPY . /app/
RUN npm run build
# Second stage
FROM nginx:latest
COPY --from=build /app/dist/nftmarketplace-frontend /usr/share/nginx/html
We create the image with the command docker build -t nftmarketplace-angular-app .
Run the image we just create as a container with the command docker run -d --name nftmarketplace-angular nftmarketplace-angular-app
In order to deploy our angular application in jenkins we create a Freestyle project and choose in general configuration a Github project and we give the url of our github project
In the Source Code Management section we give our repository url and because our reposifory is private we needed to create a github deploy key for jenkins.
In the Build Triggers section we told jenkins to build the project everytime we push a commit.
And the final configuration section is to specify the build steps.
Our project is built successfully.
This is a dashboard of the last success, failure and duration of our projects
To create the docker image we added a Dockerfile
in our cloud-gateway.
FROM openjdk:17
COPY target/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Now we build our image using the command docker build -t nftmarketplace-cloudgateway .
One of the drawbacks we can think about this Dockerfile
approach is no matter small changes we did in our API project like if we update one dependency in pom.xml
this Dockerfile
will build the whole image again so we will use the docker multi stage builds and we create another docker file Dockerfile-layered
FROM eclipse-temurin:17.0.4.1_1-jre as builder
WORKDIR extracted
ADD target/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:17.0.4.1_1-jre
WORKDIR application
COPY --from=builder extracted/dependencies/ ./
COPY --from=builder extracted/spring-boot-loader/ ./
COPY --from=builder extracted/snapshot-dependencies/ ./
COPY --from=builder extracted/application/ ./
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
Let's build the second image using the command docker build -t nftmarkeplace-cloudgateway-layered -f Dockerfile-layered .
We can check if our images are built successfully with the command docker images
In the microservices we add application-docker.properties
.
spring.data.mongodb.host=mongo
spring.data.mongodb.port=27017
spring.data.mongodb.database= nftmarketplace
server.port=9191
spring.zipkin.base-url=http://zipkin:9411
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://service-registry:8761/eureka
Because the service-registry doesn't have a database we add the following application-docker.properties
.
server.port=9191
spring.zipkin.base-url=http://zipkin:9411
To create the other images we used a librairy called Jib from google which is mainly used to build containers from java application without using the docker file. We add the plugin in the pom.xml
of our microservices-parent.
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<from>
<image>eclipse-temurin:17.0.4.1_1-jre</image>
</from>
<to>
<image>registry-1.docker.io/ahmedbentaj/${project.artifactId}</image>
</to>
</configuration>
</plugin>
We used this command mvn clean compile jib:build to build the docker image and push it to DockerHub.
The images of our services are built successfully.
And the final step is to run all this docker containers, and to do so we will create a docker-compose.yml
in the microservice-parent.
version: '3.7'
services:
## Mongo Docker Compose Config
mongo:
container_name: mongo
image: mongo:4.4.14-rc0-focal
restart: always
ports:
- "27017:27017"
expose:
- "27017"
volumes:
- ./mongo-data:/data/db
## Zipkin
zipkin:
image: openzipkin/zipkin
container_name: zipkin
ports:
- "9411:9411"
## Eureka Server
service-registry:
image: ahmedbentaj/service-registry:latest
container_name: service-registry
ports:
- "8761:8761"
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- zipkin
cloud-gateway:
image: ahmedbentaj/cloud-geteway:latest
container_name: cloud-gateway
ports:
- "9191:9191"
expose:
- "9191"
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- zipkin
- service-registry
## category-Service Docker Compose Config
category-service:
container_name: category-service
image: ahmedbentaj/category-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## collecttion-Service Docker Compose Config
collection-service:
container_name: collection-service
image: ahmedbentaj/collection-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## nft-Service Docker Compose Config
nft-service:
container_name: nft-service
image: ahmedbentaj/nft-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## image-Service Docker Compose Config
image-service:
container_name: image-service
image: ahmedbentaj/image-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## user-Service Docker Compose Config
user-service:
container_name: user-service
image: ahmedbentaj/user-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## cart-Service Docker Compose Config
cart-service:
container_name: cart-service
image: ahmedbentaj/cart-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
## transaction-Service Docker Compose Config
transaction-service:
container_name: transaction-service
image: ahmedbentaj/transaction-service:latest
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- mongo
- service-registry
- cloud-gateway
And we type the command docker compose up -d, as you can see all our services are strated successfully.