Belajar Relasi Mongo Db Dengan Studi Kasus Rental Mobil

Sebelumnya kita sudah belajar tentang membuat applikasi spring boot sederhana dengan mongodb di artikel ini https://undebugable.blogspot.com/2026/01/spring-dan-mongodb-user-login.html, selanjutnya mari buat studi kasus yang lebih komplek dan memberikan pemahaman lebih lanjut tentang MongoDB pada Spring Boot.



Belajar Relasi MongoDB dengan Spring Boot

Studi Kasus: Platform Rental Mobil Berbasis Owner

Pendahuluan

Banyak developer yang sudah nyaman dengan relasi di relational database (JOIN, foreign key, dll), tapi mulai bingung ketika masuk ke MongoDB yang bersifat document-based. Artikel ini dibuat untuk menjembatani kebingungan tersebut dengan studi kasus nyata namun sederhana.

Kita akan membangun platform rental mobil, di mana:

  • Ada perusahaan rental sebagai platform

  • Ada Owner (pemilik mobil)

  • Setiap owner bisa memiliki banyak mobil

  • Mobil-mobil tersebut disewakan melalui platform

Fokus artikel ini BUKAN booking atau pembayaran, melainkan belajar relasi data di MongoDB menggunakan Spring Boot.

Cerita Bisnis (Business Story)

Bayangkan sebuah perusahaan rental mobil yang tidak memiliki mobil sendiri. Sebagai gantinya:

  • Orang-orang bisa mendaftarkan diri sebagai owner

  • Owner dapat mendaftarkan satu atau lebih mobil

  • Platform bertugas mengelola data, listing, dan nantinya transaksi

Dengan cerita ini, kita mendapatkan relasi yang sangat umum:

Satu Owner memiliki banyak Mobil

Memahami Relasi di MongoDB

MongoDB bukan relational database. Tidak ada JOIN bawaan seperti di SQL. Namun, MongoDB menyediakan beberapa pola relasi:

  1. Embedded Document

  2. Referenced Document

  3. Hybrid

Untuk kasus rental mobil, pilihan terbaik adalah:

Referenced Document

Kenapa?

  • Owner bisa punya banyak mobil

  • Data mobil bisa bertambah besar

  • Mobil sering diakses terpisah dari owner

Desain Collection MongoDB

Collection: owners

{
     "_id": "owner001",
     "name": "Budi Santoso",
     "phone": "08123456789",
     "email": "budi@mail.com"
}

Collection: cars

{
     "_id": "car123",
     "ownerId": "owner001",
     "type": "MPV",
     "brand": "Toyota",
     "model": "Avanza",
     "year": 2022,
     "totalSeats": 7,
     "color": "BLACK",
     "plateNumber": "D 1234 AB",
     "dailyPrice": 350000,
     "status": "AVAILABLE"
}

Relasi terjadi lewat field: ownerId

Relasi Data

Secara konseptual:

Owner (1) ------ (N) Car

Tidak ada JOIN. Relasi dibangun lewat query berdasarkan ownerId.

Domain Model di Spring Boot

Owner Entity

Entity Owner merepresentasikan pemilik kendaraan yang mendaftarkan mobilnya ke dalam sistem rental. Dalam konteks bisnis, owner bukan penyewa, melainkan pihak ketiga yang mempercayakan asetnya kepada perusahaan rental untuk dikelola dan disewakan. Satu owner dapat memiliki lebih dari satu mobil, sehingga relasi yang terbentuk bersifat one-to-many antara Owner dan Car.

Pada desain MongoDB, Owner disimpan sebagai collection terpisah agar data pemilik tetap terpusat, konsisten, dan mudah dikelola. Informasi seperti nama, nomor kontak, dan email bersifat relatif stabil dan tidak perlu diduplikasi di setiap dokumen mobil. Oleh karena itu, pendekatan reference (menyimpan ownerId di Car) dipilih dibandingkan embedding, karena lebih fleksibel untuk pengembangan fitur lanjutan seperti verifikasi pemilik, laporan pendapatan per owner, atau perubahan data pemilik tanpa harus mengubah banyak dokumen sekaligus.

Dengan memisahkan Owner sebagai entity mandiri, sistem menjadi lebih modular, scalable, dan mencerminkan struktur bisnis rental mobil yang sesungguhnya.

package com.yusuf.springmongoclean.domain.rental;

import com.yusuf.springmongoclean.enumerator.CarColor;
import com.yusuf.springmongoclean.enumerator.CarStatus;
import com.yusuf.springmongoclean.enumerator.CarType;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "cars")
public record Car(
@Id String id,
String ownerId,


CarType type,
String brand,
String model,
int year,


int totalSeats,
CarColor color,


String plateNumber,
int dailyPrice,
CarStatus status
) {}

Car Entity

Entity Car merepresentasikan kendaraan yang disewakan melalui platform rental. Setiap mobil merupakan aset milik seorang owner dan terhubung ke entity Owner melalui ownerId. Dalam sistem ini, Car menjadi pusat aktivitas utama karena hampir seluruh proses bisnis, seperti pencarian, ketersediaan, dan penyewaan, berinteraksi langsung dengan data mobil.

Car menyimpan informasi yang bersifat spesifik terhadap kendaraan, seperti jenis mobil, merek, model, tahun produksi, jumlah kursi, warna, nomor plat, harga sewa harian, serta status ketersediaan. Data ini bersifat dinamis dan dapat berubah seiring waktu, misalnya saat mobil sedang disewa, tidak tersedia, atau dalam perawatan. Karena karakteristik tersebut, Car dirancang sebagai collection terpisah yang mereferensikan Owner, bukan sebagai data yang di-embed di dalam Owner.

Pendekatan ini memungkinkan sistem untuk melakukan query secara efisien berdasarkan kriteria mobil, seperti jenis kendaraan, kapasitas tempat duduk, atau status ketersediaan, tanpa harus memuat seluruh data pemilik. Selain itu, desain ini memudahkan pengembangan fitur lanjutan seperti pencarian mobil berdasarkan filter, histori penyewaan, dan penghitungan pendapatan per mobil, sekaligus menjaga struktur data tetap bersih dan terukur seiring pertumbuhan jumlah kendaraan di dalam sistem.

package com.yusuf.springmongoclean.domain.rental;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document(collection = "owners")
public record Owner(
@Id String id,
String name,
String phone,
String email
) {}

Enum untuk Data Konsisten

Jenis Mobil

CarType digunakan untuk mengklasifikasikan jenis kendaraan berdasarkan fungsi dan karakteristik utamanya. Dalam sistem rental mobil, pengelompokan ini sangat penting karena kebutuhan penyewa berbeda-beda, ada yang mencari mobil kecil untuk mobilitas harian, ada yang membutuhkan kendaraan keluarga, hingga kendaraan dengan kapasitas besar untuk keperluan komersial atau perjalanan kelompok.

CarType direpresentasikan sebagai enum agar nilainya konsisten, terbatas, dan mudah divalidasi di level aplikasi. Dengan pendekatan ini, sistem terhindar dari data bebas yang tidak terkontrol seperti variasi penulisan atau klasifikasi yang ambigu. Contoh tipe mobil yang umum digunakan antara lain CITY untuk mobil kecil, MPV untuk kendaraan keluarga, SUV untuk kebutuhan medan bervariasi, serta VAN atau MINIBUS untuk kapasitas penumpang yang lebih besar.

Penggunaan CarType juga mempermudah proses pencarian dan penyaringan mobil di dalam sistem. Penyewa dapat dengan cepat menemukan kendaraan yang sesuai dengan kebutuhannya berdasarkan kategori, sementara dari sisi teknis, query menjadi lebih sederhana dan efisien. Selain itu, klasifikasi ini membuka ruang pengembangan fitur lanjutan seperti analisis permintaan berdasarkan jenis mobil atau penentuan harga dinamis per kategori kendaraan.

package com.yusuf.springmongoclean.enumerator;

public enum CarType {
CITY_CAR,
MPV,
SUV,
SEDAN,
HATCHBACK,
PICKUP,
VAN,
LUXURY
}

Warna Mobil

CarColor merepresentasikan warna kendaraan sebagai salah satu atribut identitas visual mobil. Meskipun tidak memengaruhi fungsi teknis kendaraan, warna sering kali berpengaruh terhadap preferensi penyewa, kemudahan identifikasi kendaraan di lapangan, serta pencatatan aset oleh perusahaan rental.

Dalam sistem, CarColor direpresentasikan sebagai enum untuk menjaga konsistensi data dan menghindari variasi penulisan yang tidak terkontrol, seperti perbedaan antara “hitam”, “black”, atau “blk”. Dengan membatasi pilihan warna pada nilai-nilai yang telah ditentukan, sistem menjadi lebih mudah divalidasi dan lebih stabil dalam jangka panjang. Contoh warna yang umum digunakan antara lain BLACK, WHITE, SILVER, GRAY, dan RED.

Penggunaan CarColor sebagai enum juga memberikan keuntungan pada sisi teknis, terutama saat melakukan filtering atau pelaporan data. Perusahaan rental dapat dengan mudah menganalisis preferensi warna yang paling sering disewa atau membantu operasional dalam mengidentifikasi kendaraan dengan cepat. Walaupun terlihat sederhana, atribut ini berkontribusi pada kerapihan model data dan meningkatkan kualitas informasi yang disimpan di dalam sistem.

package com.yusuf.springmongoclean.enumerator;

public enum CarColor {
BLACK,
WHITE,
SILVER,
RED,
BLUE,
GREY
}

Status Mobil

package com.yusuf.springmongoclean.enumerator;

public enum CarStatus {
AVAILABLE,
RENTED,
MAINTENANCE
}

Repository Layer

OwnerRepository

package com.yusuf.springmongoclean.repository.rental;

import com.yusuf.springmongoclean.domain.rental.Car;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface CarRepository extends MongoRepository<Car, String> {

List<Car> findByOwnerId(String ownerId);
}

CarRepository

package com.yusuf.springmongoclean.repository.rental;

import com.yusuf.springmongoclean.domain.rental.Car;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.util.List;

public interface CarRepository extends MongoRepository<Car, String> {

List<Car> findByOwnerId(String ownerId);
}

Inilah inti relasi MongoDB di Spring:

Query berdasarkan reference field

Contoh Alur Penggunaan

  1. Owner mendaftar ke platform

  2. Owner mendapatkan ownerId

  3. Owner mendaftarkan beberapa mobil menggunakan ownerId

  4. Sistem dapat menampilkan semua mobil milik owner tertentu

Endpoint yang akan dibuat:

POST /owners
POST /cars
GET /owners/{id}/cars


MongoDB bukan berarti "tanpa relasi". Yang berubah hanyalah cara berpikirnya.

Dengan pendekatan reference seperti ini, kita mendapatkan:

  • Struktur data yang bersih

  • Query yang eksplisit

  • Sistem yang mudah dikembangkan

Studi kasus rental mobil ini memberikan fondasi kuat untuk memahami relasi di MongoDB sebelum masuk ke fitur yang lebih kompleks.

CRUD API untuk Owner & Car

Setelah memahami relasi data, langkah logis berikutnya adalah membangun CRUD API untuk mengelola Owner dan Car. Pada bagian ini, kita fokus pada API design + implementasi Spring Boot.

DTO (Request Object)

OwnerRequest

package com.yusuf.springmongoclean.dto.rental;

public record OwnerRequest(
String name,
String phone,
String email
) {}

CarRequest

package com.yusuf.springmongoclean.dto.rental;

import com.yusuf.springmongoclean.enumerator.CarColor;
import com.yusuf.springmongoclean.enumerator.CarType;

public record CarRequest(
String ownerId,
CarType type,
String brand,
String model,
int year,
int totalSeats,
CarColor color,
String plateNumber,
int dailyPrice
) {}

Service Layer

OwnerService

package com.yusuf.springmongoclean.service.rental;

import com.yusuf.springmongoclean.domain.rental.Owner;
import com.yusuf.springmongoclean.dto.rental.OwnerRequest;
import com.yusuf.springmongoclean.repository.rental.OwnerRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class OwnerService {


private final OwnerRepository repository;


public OwnerService(OwnerRepository repository) {
this.repository = repository;
}


public Owner create(OwnerRequest request) {
return repository.save(new Owner(
null,
request.name(),
request.phone(),
request.email()
));
}


public List<Owner> findAll() {
return repository.findAll();
}


public Owner findById(String id) {
return repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Owner not found"));
}
}

Service Layer

CarService    

package com.yusuf.springmongoclean.service.rental;

import com.yusuf.springmongoclean.domain.rental.Car;
import com.yusuf.springmongoclean.dto.rental.CarRequest;
import com.yusuf.springmongoclean.enumerator.CarStatus;
import com.yusuf.springmongoclean.repository.rental.CarRepository;
import com.yusuf.springmongoclean.repository.rental.OwnerRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CarService {


private final CarRepository carRepository;
private final OwnerRepository ownerRepository;


public CarService(CarRepository carRepository, OwnerRepository ownerRepository) {
this.carRepository = carRepository;
this.ownerRepository = ownerRepository;
}


public Car create(CarRequest request) {
ownerRepository.findById(request.ownerId())
.orElseThrow(() -> new IllegalArgumentException("Owner not found"));


return carRepository.save(new Car(
null,
request.ownerId(),
request.type(),
request.brand(),
request.model(),
request.year(),
request.totalSeats(),
request.color(),
request.plateNumber(),
request.dailyPrice(),
CarStatus.AVAILABLE
));
}


public List<Car> findByOwner(String ownerId) {
return carRepository.findByOwnerId(ownerId);
}
}

Controller Layer

OwnerController

package com.yusuf.springmongoclean.controller.rental;

import com.yusuf.springmongoclean.domain.rental.Owner;
import com.yusuf.springmongoclean.dto.rental.OwnerRequest;
import com.yusuf.springmongoclean.service.rental.OwnerService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/owners")
public class OwnerController {


private final OwnerService service;


public OwnerController(OwnerService service) {
this.service = service;
}


@PostMapping
public Owner create(@RequestBody OwnerRequest request) {
return service.create(request);
}


@GetMapping
public List<Owner> findAll() {
return service.findAll();
}


@GetMapping("/{id}")
public Owner findById(@PathVariable String id) {
return service.findById(id);
}
}

CarController

package com.yusuf.springmongoclean.controller.rental;

import com.yusuf.springmongoclean.domain.rental.Car;
import com.yusuf.springmongoclean.dto.rental.CarRequest;
import com.yusuf.springmongoclean.service.rental.CarService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/cars")
public class CarController {


private final CarService service;


public CarController(CarService service) {
this.service = service;
}


@PostMapping
public Car create(@RequestBody CarRequest request) {
return service.create(request);
}


@GetMapping("/owner/{ownerId}")
public List<Car> findByOwner(@PathVariable String ownerId) {
return service.findByOwner(ownerId);
}
}



Create owner


Get owner


Create cars dengan id milik owner


Mendapatkan cars dengan id milik owner


Dengan API ini, kita sudah:

  • Mengelola Owner dan Car

  • Menghubungkan relasi 1-to-many

  • Menerapkan clean separation (Controller–Service–Repository)

Ini adalah fondasi kuat untuk fitur lanjutan seperti booking, availability, dan payment.






Comments

Popular posts from this blog

Numpang Kerja Remote dari Bandung Creative Hub

Debugging PHP Web dengan XDebug di Intellij IDEA (PHP STORM)

Numpang Kerja Remote dari Bandung Digital Valley