Skip to content

angelspitfire/spring-hexagonal-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Getting Started

Developer Technical Challenge

  • Use any architecture you prefer.
  • Use modern Java (version 17 or higher).
  • Use Spring Boot.
    • Additional libraries can be added as needed.
  • Use any database (NoSQL preferred, embedded recommended).
  • Ensure the code is tested.
  • The service should be easy to run (e.g., using docker-compose).
  • The service should expose a functional REST API accessible via Swagger UI.
  • The service should start with some sample data.

Business Case: Creative Management

Entity Description
Brand A retailer of product(s).
Creative Digital marketing files for advertisements, associated with a brand campaign. A single brand can have many creatives.
Campaign A collection of creatives aimed at achieving an advertising goal. A brand can run multiple campaigns, each containing many creatives.
  • Example data models for each entity are provided in the com.hexagonal.challenge.model package. You may add additional fields if necessary. The 'creativeUrl' in a creative is a string representing a URL to a file stored in a CDN.

  • Create a simple web application for managing creatives.

  • Users should be able to:

    • Create a brand.
    • Create a campaign for a brand.
    • Upload a creative for a brand campaign.
  • Authentication is not required.

Example Resources

Brand:

  • Create a brand.
  • List all brands.

Campaign:

  • Create a campaign for a brand.
  • List all campaigns for a brand.

Creative:

  • Upload a creative for a brand campaign.
  • List creatives for a brand campaign.

Solution Overview

This project provides a system for managing brands, campaigns, and creatives within a digital advertising platform. Using Spring Boot and MongoDB, it offers a robust backend infrastructure for various advertising operations. Designed for scalability and maintenance, this solution is suitable for both small startups and large enterprises looking to optimize their advertising workflows.

Architecture Overview

The system's architecture is designed following the Hexagonal Architecture approach, also known as Ports and Adapters. This design pattern aims to create a loosely coupled application that isolates the core logic from external concerns. By structuring the application into several key layers, each responsible for a distinct aspect of the application's functionality, we ensure flexibility and ease of maintenance:

  • Controller Layer (Adapters): Acts as the primary entry point for HTTP requests, exposing RESTful APIs for interacting with the application. This layer adapts requests from the external world into a format that the application can use.
  • Service Layer (Application Core): Implements the business logic and use case execution. It serves as the application's core, where the main functionalities and rules are processed.
  • Repository Layer (Adapters): Manages CRUD operations with the MongoDB database, acting as an adapter that allows the application core to interact with the database without being coupled to it.
  • Model Layer: Defines the structure of the data entities used across the application, central to the application's domain.
  • Domain Layer: Represents the core business logic and entities. It is the heart of the application, where the business rules and domain logic reside.
  • Infrastructure Layer (Adapters): Configures the technical infrastructure, including database connections and other system-wide settings. This layer includes adapters for various external interfaces the application interacts with.

By adhering to the Hexagonal Architecture, we ensure that the application's core logic is independent of external interfaces and frameworks, making the system more resilient to changes in technology or business requirements.

Key Components

flowchart TD
    subgraph "Spring Boot Application"
        CP[Controller Port]
        SP[Service Port]
        RP[Repository Port]
    end

    CP --> SP
    SP --> RP

    subgraph MongoDB
        BD[BrandDocument]
        CD[CampaignDocument]
        CrD[CreativeDocument]
    end

    RP --> MongoDB
    BrandController --> BrandService
    CampaignController --> CampaignService
    CreativeController --> CreativeService
    BrandService --> BrandRepository
    CampaignService --> CampaignRepository
    CreativeService --> CreativeRepository
    BrandRepository --> BD
    CampaignRepository --> CD
    CreativeRepository --> CrD

Loading

Classes

classDiagram
    class BrandController {
        +createBrand(brand: Brand): ResponseEntity<Brand>
        +listBrands(page: int, size: int): ResponseEntity<List<Brand>>
        +getBrandById(brandId: String): ResponseEntity<Brand>
        +updateBrand(brandId: String, brand: Brand): ResponseEntity<Brand>
        +deleteBrand(brandId: String): ResponseEntity<?>
        +createCampaignForBrand(brandId: String, campaign: Campaign): ResponseEntity<Campaign>
        +listCampaignsForBrand(brandId: String, page: int, size: int): ResponseEntity<List<Campaign>>
        +listCreativesForCampaign(brandId: String, campaignId: String, page: int, size: int): ResponseEntity<List<Creative>>
        +uploadCreative(brandId: String, campaignId: String, file: MultipartFile, name: String, description: String): ResponseEntity<Creative>
    }
    class CampaignController {
        +createCampaign(campaign: Campaign): ResponseEntity<Campaign>
        +listCampaigns(page: int, size: int): ResponseEntity<List<Campaign>>
        +getCampaignById(campaignId: String): ResponseEntity<Campaign>
        +updateCampaign(campaignId: String, campaign: Campaign): ResponseEntity<Campaign>
        +deleteCampaign(campaignId: String): ResponseEntity<?>
    }
    class CreativeController {
        +createCreative(creative: Creative): ResponseEntity<Creative>
        +listCreatives(page: int, size: int): ResponseEntity<List<Creative>>
        +getCreativeById(creativeId: String): ResponseEntity<Creative>
        +updateCreative(creativeId: String, creative: Creative): ResponseEntity<Creative>
        +deleteCreative(creativeId: String): ResponseEntity<?>
    }

    class ManageCampaignUseCase {
        +createCampaign(String brandId, Campaign campaign)
        +listCampaigns(PageRequest pageRequest)
        +getCampaignById(String campaignId): Optional<Campaign>
        +updateCampaign(String campaignId, Campaign campaign): Optional<Campaign>
        +deleteCampaign(String campaignId): boolean
        +createCampaignForBrand(String brandId, Campaign campaign)
        +findCampaignsByBrandId(String brandId, PageRequest pageRequest)
        +findCreativesByBrandIdAndCampaignId(String brandId, String campaignId, PageRequest pageRequest)
    }
    class ManageCreativeUseCase {
        +listCreatives(PageRequest pageRequest): List<Creative>
        +getCreativeById(String creativeId): Optional<Creative>
        +updateCreative(String creativeId, Creative creative): Optional<Creative>
        +deleteCreative(String creativeId): boolean
    }

    class BrandRepository
    class CampaignRepository
    class CreativeRepository

    class BrandEntity {
        -id: String
        -name: String
        -description: String
    }
    class CampaignEntity {
        -id: String
        -name: String
        -description: String
        -brandId: String
    }
    class CreativeEntity {
        -id: String
        -name: String
        -description: String
        -creativeUrl: String
        -campaignId: String
    }

    class Brand
    class Campaign
    class Creative

    class MongoDBConfig {
        +mongoTemplate(): MongoTemplate
    }
    class BrandRepositoryImpl {
        +save(BrandEntity brand): BrandEntity
        +findById(String id): Optional<BrandEntity>
        +findAll(PageRequest pageRequest): List<BrandEntity>
        +deleteById(String id): void
    }
    class CampaignRepositoryImpl {
        +save(CampaignEntity campaign): CampaignEntity
        +findById(String id): Optional<CampaignEntity>
        +findAll(PageRequest pageRequest): List<CampaignEntity>
        +deleteById(String id): void
    }
    class CreativeRepositoryImpl {
        +save(CreativeEntity creative): Creative
        +findById(String id): Optional<CreativeEntity>
        +findAll(PageRequest pageRequest): List<CreativeEntity>
        +deleteById(String id): void
        +findByCampaignId(String campaignId, PageRequest pageRequest): List<CreativeEntity>
        +findByBrandIdAndCampaignId(String brandId, String campaignId, PageRequest pageRequest): List<CreativeEntity>
    }

    class WebSecurity
    class WebMvcConfig
    class SwaggerConfig

    BrandController --> ManageCampaignUseCase
    CampaignController --> ManageCampaignUseCase
    CreativeController --> ManageCreativeUseCase
    ManageCampaignUseCase --> BrandRepository
    ManageCampaignUseCase --> CampaignRepository
    ManageCreativeUseCase --> CreativeRepository
    BrandRepositoryImpl --> MongoDBConfig
    CampaignRepositoryImpl --> MongoDBConfig
    CreativeRepositoryImpl --> MongoDBConfig
    BrandRepository --> BrandRepositoryImpl
    CampaignRepository --> CampaignRepositoryImpl
    CreativeRepository --> CreativeRepositoryImpl
    MongoDBConfig --> WebSecurity
    MongoDBConfig --> WebMvcConfig
    MongoDBConfig --> SwaggerConfig
Loading

Technical Stack

  • Programming Language: Java 17
  • Framework: Spring Boot
  • Database: MongoDB (embedded and real instance on docker)
  • Build Tool: Maven

Running the Application

To get the application up and running on your local machine, follow these steps:

  1. Prerequisites:

    • Java 17 or newer installed.
    • MongoDB running on the default port (27017).
    • Maven installed for building the application.
    • Docker installed (optional, for running MongoDB in a container).
    • Docker Compose installed (optional, for running MongoDB in a container).
  2. Clone the Repository: Clone the project repository to your local machine using the following command:

     git clone [email protected]:angelspitfire/challenge.git
     cd challenge
  3. Build the Application:

   mvn clean package
  1. Run the Application:
       java -jar target/challenge-0.0.1-SNAPSHOT.jar

or alternatively, you can run the application using the following command:

   mvn spring-boot:run

or using docker-compose:

   docker-compose up
  1. Access the Application: The application will be accessible at http://localhost:8080. You can use tools like Postman or cURL to interact with the RESTful APIs provided by the application. The Swagger UI is also available at http://localhost:8080/swagger-ui.html, providing a user-friendly interface for exploring and testing the APIs.

Considerations

  • Embedded MongoDB for Local Use: The application uses an embedded MongoDB instance by default for local development. To switch to a standalone MongoDB instance, update the connection details in the application.yaml file.

  • MongoDB for docker-compose: The application automatically runs a MongoDB instance when running with docker-compose. This setup is designed to bypass the need for local MongoDB installation, especially useful due to a bug on Apple Silicon machines related to de.flapdoodle.embed.mongo.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published