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:
Embedded Document
Referenced Document
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
Collection: cars
Relasi terjadi lewat field: ownerId
Relasi Data
Secara konseptual:
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
Owner mendaftar ke platform
Owner mendapatkan
ownerIdOwner mendaftarkan beberapa mobil menggunakan
ownerIdSistem dapat menampilkan semua mobil milik owner tertentu
Endpoint yang akan dibuat:
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 |
| 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
Post a Comment