RiaChoi Text Logo.
Spring Boot Backend Delivery App

SpringBoot

Docker

AWS

AI

UnitTest

Spring Boot Backend Delivery App

Build a scalable AI-powered delivery platform with Spring Boot, Docker, Amazon Web Services, and Gemini.

Ria ChoiMay 16th, 2026

Sparta Boot Camp
Sparta Boot Camp

This project was part of the Sparta Java-Spring Boot Backend Boot Camp.

 

📣 A Note to Hiring Team: You can skip right to Chapter 5 for code reviews.

 

🍱 Table of Contents

  1. Project Overview
  2. System Architecture
  3. Development Workflow - How we worked together
  4. My Role
  5. Code Implementation
  6. Wrapping Up

 

ℹ️ Project Overview

Today Eats Java Backend Project
Today Eats: Java Backend Project

TodayEats is an AI-powered delivery platform built with Spring Boot, designed to explore real-world backend architecture and infrastructure design beyond simple CRUD development.

Scalable and maintainable backend system

  • PostgreSQL, Redis, Docker, AWS, and GitHub Actions for CI/CD automation.

This project is built on top of a modular monolithic architecture to reduce operational complexity, but we kept scalability in mind when designing the system.

 

You can check the README file in the repository for more information.
GitHub: https://github.com/GolemOnce/Sparta-TodayEats

 

⚙️ System Architecture

Architecture
Architecture
  • Only Backend - Spring Boot
    • Spring Web, Spring Data JPA, PostgreSQL Driver, Spring Security, Lombok, JUnit5 & Mockito
  • Database - PostgreSQL
  • AI - Gemini AI Studio API
  • Infrastructure - Docker, AWS
  • DevOps - GitHub Actions
  • Caching - Redis
  • Documentation - Swagger / OpenAPI

 

🏗️ Development Workflow - How we worked together

This bootcamp was online, and the camp decided to use Zap as a communication platform.
Thanks to Zap, we were able to communicate without latency or delay whenever we wanted to talk.

Our team also made sure to put all the documents in one place, so no member missed anything and on schedule for development.

the notion page
The Notion Page

As you can see in the photo, we have documented every detail of the development and the code on a single Notion page.

The team followed the Agile process, but we tried to stick to the initial plan.

Thanks to AI, the actual coding stage didn't take us so long; it was mostly planning and structuring the whole project together.

API Specification
You can see how we went so detailed from the API Specification

We also made sure to use the GitHub Kanban board (in the project tab) and the Git Issues to share the coding process and how far each person got.

Blog image
Blog image

We even had our own template (Issue and Pull Request) to keep everything looking clean for each member.

Blog image

 

My Role

Blog image

My primary responsibilities in this project included implementing the Menu domain and integrating the Gemini AI API.

I also designed the AI request logging flow to track prompts and responses for better monitoring and future improvements.

 

🔤 Code Implementation

Menu Domain

1. Query design based on "who is viewing"
Blog image
MenuRepository.java

I split them into purpose-specific JPQL queries.

  • findOwnerMnuesByStoredId
  • findOrderableMenusByStoreId
  • findVisibleMunusByStoreAndCategory

=> A key design decision was treating "hidden" and "sold out" as two separate status fields.

Full Code Review Available Here

 

2. Revalidation of ownership at the service layer
Blog image
MenuService.java

For every mutating operation (update, delete, status change), I added a validateStoreOwner() check in the service layer that compares the store's actual owner against the requester's ID.

Full Code Review Available Here

 

3. Soft delete + Cascading deletion
Blog image
Menu.java
Blog image
MenuRepository.java
Blog image
MenuService.java

Menus use soft deletion via inheriting from BaseEntity, recording deletedAt / deletedBy instead of physically removing rows.

Full Code Review Available Here

 

AI Domain

1. Gemini API integration with granular failure handling
Blog image
GeminiClient.java

GeminiClient calls the Gemini API directly via RestTemplate. Rather than treating all failures the same, I split failure handling into three distinct cases:

  • ResourceAccessException (network timeout) → AI_TIMEOUT (504)
  • Any other call-level exception (invalid key, server error, etc.) → AI_API_ERROR (502)
  • A response comes back, but candidates / content / parts / text is missing or empty at any level → AI_RESPONSE_EMPTY (502)

Full Code Review Available Here

 

2. Prompt shaping + a double safeguard on response length
Blog image
AiService.java

Rather than forwarding the store owner's raw prompt to Gemini as-is, I appended an instruction — " 답변을 최대한 간결하게 50자 이하로" ("keep the answer under 50 characters, as concise as possible") — to the prompt before sending it.

Full Code Review Available Here

 

2. Storing the raw prompt and the shaped prompt separately
Blog image
AiRequestLogEntity.java

The AiRequestLogEntity stores three fields separately: the original user prompt, the shaped prompt actually sent to the API (requestPrompt), and the response.

Full Code Review Available Here

 

4. Role-based access control
Blog image
AiController.java

The AI description generation endpoint is restricted to @PreAuthorize("hasRole('OWNER')"), so only store owners can trigger it.

Full Code Review Available Here

 

Testing

I wrote unit tests mocking GeminiClient and AiRequestLogRepository to verify that the prompt is correctly combined with the appended instruction before being sent, and that the response gets persisted to the log correctly.

Full Code Review Available Here

 

Wrapping Up

This project was my first time working with other programmers; I learned to write code that collaborates with people, follow workflows, and integrate AI.

Share

Related contents