Anda ingin merencanakan perjalanan ke Goa. Anda memiliki waktu lima hari, anggaran 25.000 rupee, dan preferensi yang jelas untuk pantai dan makanan laut. Biasanya, ini berarti membuka sepuluh tab browser, membaca postingan forum yang sudah usang, dan menyusun rencana perjalanan secara manual. Sebaliknya, bayangkan mengirimkan satu permintaan POST dan mendapatkan kembali rencana harian terstruktur dengan saran makanan, daftar aktivitas, dan pembagian anggaran yang tepat. Itulah yang diberikan oleh proyek ini.
Kita akan membangun REST API menggunakan Spring Boot dan Azure OpenAI. API ini menerima destinasi, anggaran, durasi, dan minat. API akan mengembalikan JSON bersih yang dapat langsung dirender oleh frontend atau aplikasi seluler. Tanpa scraping. Tanpa rencana perjalanan yang dikodekan secara keras (hardcoded). Hanya model AI yang diberi instruksi untuk bertindak sebagai perencana perjalanan.
Apa yang Dikembalikan oleh API
Responsnya bukan berupa blok teks Markdown yang harus Anda bedah menggunakan regex. Respons tersebut adalah objek JSON terstruktur yang berisi aktivitas harian, rekomendasi makanan, dan rincian anggaran. Untuk perjalanan ke Goa, Anda mungkin menerima segmen hari pertama yang mengalokasikan 500 rupee untuk sarapan di warung pinggir pantai, pagi hari di Palolem, dan makan malam makanan laut di sore hari di lokasi tertentu. Setiap hari menyertakan slot waktu, estimasi biaya, dan tag seperti "beach" atau "food." Struktur ini penting karena aplikasi perjalanan modern tidak ingin mengurai paragraf. Mereka menginginkan objek yang dapat dipetakan ke RecyclerView atau komponen React.
Stack yang Digunakan dan Mengapa Cocok
Proyek ini menggunakan Spring Boot 3.5 dengan Spring AI. Spring AI adalah bagian yang krusial. Ia menyediakan abstraksi ChatModel yang terpadu sehingga Anda tidak perlu menulis klien HTTP mentah terhadap Azure OpenAI. Anda cukup menukar dependensi dan properti, bukan kode layanan.
Anda memerlukan empat dependensi dalam file build Anda:
spring-boot-starter-webuntuk lapisan REST.spring-ai-starter-model-azure-openaiuntuk terhubung ke LLM melalui antarmuka Spring AI.springdoc-openapiuntuk dokumentasi Swagger otomatis.Lombokuntuk mengurangi boilerplate pada POJO request dan response Anda.
Spring AI berada di antara logika bisnis Anda dan penyedia LLM. Penempatan ini disengaja. Hal ini menjaga kelas @Service Anda tetap bersih dan agnostik terhadap penyedia.
Prompt Engineering dengan PromptTemplates
Melakukan hardcode prompt di dalam string Java adalah cara cepat untuk membuat perangkat lunak yang sulit dipelihara. Jika tim produk memutuskan bahwa AI harus terdengar lebih santai atau menolak estimasi anggaran di atas ambang batas tertentu, Anda tidak perlu mengompilasi ulang layanan Anda.
Spring AI menyediakan PromptTemplate. Anda menyimpan kerangka prompt dalam file resource atau string template khusus, dengan menyisakan placeholder untuk variabel seperti {destination}, {budget}, {days}, dan {interests}. Saat runtime, layanan membuat objek Prompt dan menyuntikkan nilai pengguna.
Pisahkan pesan sistem dari pesan pengguna. Gunakan pesan sistem untuk menentukan persona. Misalnya, Anda memberi tahu model bahwa ia adalah perencana perjalanan yang berspesialisasi dalam destinasi India, sadar anggaran, dan ketat dalam hanya mengembalikan JSON tanpa markdown fences. Gunakan pesan pengguna untuk mengirimkan detail perjalanan yang spesifik. Pemisahan ini membantu saat Anda ingin melakukan A/B test pada persona tanpa mengubah kontrak API.
Service Layer: Berkomunikasi dengan Azure OpenAI
Kelas @Service memiliki satu tugas. Ia membangun prompt, memanggil model, membersihkan respons, dan mengurai hasilnya.
Suntikkan (Inject) ChatClient atau ChatModel dari Spring AI. Render PromptTemplate dengan nilai request yang masuk, lalu panggil metode chat. Respons akan datang sebagai String. Di sinilah banyak tutorial berhenti dan kode produksi yang sebenarnya dimulai.
LLM terkadang menambahkan kata pengantar yang sopan. Anda mungkin menerima respons yang dibuka dengan "Here is your itinerary" dan kemudian menyajikan JSON yang dibungkus dengan triple backticks. Jika Anda mencoba melakukan deserialisasi secara langsung dengan Jackson, aplikasi Anda akan crash. Tambahkan metode pembantu kecil yang memindai string mentah, menemukan kurung kurawal buka pertama dan kurung kurawal tutup terakhir, lalu mengekstrak hanya payload JSON-nya. Kemudian validasi blok yang diekstrak tersebut. Pastikan field yang diperlukan ada dan nilai numerik masuk akal sebelum Anda mengembalikan objek tersebut ke controller.
Parsing defensif ini bukanlah pilihan. Ini adalah batas antara demo dan API yang andal.
Menangani Error Seperti Sistem yang Matang
API eksternal bisa gagal. Azure OpenAI akan mengembalikan error limit rate, kegagalan autentikasi, atau error 500 yang bersifat sementara. Jika Anda membiarkan error ini muncul ke pengguna sebagai stack trace, Anda akan kehilangan kredibilitas.
Gunakan @RestControllerAdvice untuk mencegat pengecualian (exception) secara global. Petakan pengecualian Spring AI, HttpClientErrorException, dan RuntimeException umum ke respons kesalahan yang konsisten. Kembalikan body JSON dengan pesan yang jelas, status HTTP seperti 429 untuk pembatasan laju (rate limits), dan detail yang cukup agar klien dapat mencoba lagi atau mencatat masalah tersebut. Pengguna harus melihat sesuatu seperti "Layanan sedang sibuk. Silakan coba lagi dalam 30 detik," bukan layar yang penuh dengan nama kelas Java.
Jangan Pernah Melakukan Hardcode pada Secret
API key Azure OpenAI Anda tidak seharusnya berada di dalam application.properties yang masuk ke Git. Eksternalisasi kunci tersebut. Gunakan variabel lingkungan (environment variables) yang dirujuk dalam konfigurasi Spring Anda, seperti ${AZURE_OPENAI_KEY} dan ${AZURE_OPENAI_ENDPOINT}. Simpan file .env lokal untuk pengembangan, tambahkan ke .gitignore, dan muat melalui relaxed binding Spring Boot. Jika sebuah kunci bocor, Anda cukup menggantinya di satu tempat daripada harus membangun ulang artefak Anda.
Pengujian Melalui Swagger
Dependensi springdoc-openapi mengekspos endpoint Swagger UI saat runtime. Setelah aplikasi Anda berjalan, buka /swagger-ui.html di browser. Anda dapat mengisi contoh Goa secara langsung: tujuan sebagai "Goa," anggaran sebagai 25000, hari sebagai 5, minat sebagai "beaches, food." Klik execute dan lihat itinerary JSON muncul. Ini memungkinkan Anda memvalidasi perubahan prompt, memverifikasi serialisasi, dan berbagi playground langsung dengan pengembang frontend sebelum salah satu pihak menulis unit test.
Mengganti Provider Tanpa Menulis Ulang Kode
Startup sering berganti provider. Mungkin kredit Azure habis, atau Anda ingin menjalankan inferensi terhadap instansi Ollama lokal untuk memangkas biaya. Karena Spring AI mengabstraksi antarmuka ChatModel, penggantiannya bersifat mekanis. Ubah dependensi Maven dari spring-ai-starter-model-azure-openai ke starter lainnya, perbarui file properties Anda dengan endpoint dan kunci yang baru, dan biarkan kelas service Anda tetap seperti semula. Kontrak API yang dilihat oleh aplikasi seluler Anda akan tetap identik.
Portabilitas tersebut membuat arsitektur ini sangat berguna untuk produk nyata. Anda tidak "menikah" dengan Azure. Anda menggunakannya sebagai salah satu mesin yang terhubung ke dalam pipeline Spring yang bersih.
Intisari Utamanya
Model AI bukanlah aplikasi Anda. Ia adalah layanan eksternal yang mengembalikan teks yang tidak terprediksi. Perlakukan ia dengan ketelitian yang sama seperti Anda memperlakukan gateway pembayaran atau API cuaca pihak ketiga. Eksternalisasi kredensial Anda. Validasi setiap respons. Bersihkan payload sebelum melakukan parsing. Tangani kesalahan secara global agar pengguna Anda tidak pernah melihat stack trace.
Biarkan AI menangani pekerjaan kreatif dalam membangun itinerary Goa dengan anggaran 25.000 rupee. Anda menangani infrastrukturnya. Ketika keduanya tetap terpisah, Anda akan mendapatkan sistem yang benar-benar siap diluncurkan.
Panduan asli yang menginspirasi artikel ini dapat ditemukan di sini.
Tertarik untuk mendiskusikan Spring AI dan proyek serupa? Bergabunglah dengan komunitas belajar GyaanSetu.
