- 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.
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.
- Create a brand.
- List all brands.
- Create a campaign for a brand.
- List all campaigns for a brand.
- Upload a creative for a brand campaign.
- List creatives for a brand campaign.
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.
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
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
- Programming Language: Java 17
- Framework: Spring Boot
- Database: MongoDB (embedded and real instance on docker)
- Build Tool: Maven
To get the application up and running on your local machine, follow these steps:
-
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).
-
Clone the Repository: Clone the project repository to your local machine using the following command:
git clone [email protected]:angelspitfire/challenge.git cd challenge
-
Build the Application:
mvn clean package
- 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
- 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 athttp://localhost:8080/swagger-ui.html
, providing a user-friendly interface for exploring and testing the APIs.
-
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.