Skip to content

Aku Dikirim ke Dunia Lain untuk Menjadi Software Engineer

Prelude

Mata anda terus memandang liar mengitari ruangan, keringat dingin membasahi sekujur tubuh anda, dan kaki anda tidak henti-hentinya gemetar. Di hadapan anda ada beberapa pria paruh baya saling berbisik satu sama lain. Terkadang mata mereka tersingkap menatap tajam ke arah anda. Salah satu dari mereka lalu berdiri, memanggil nama anda.

" Mohon maaf perusahaan kami belum bisa menerima anda." Ujar salah satu pria paruh baya itu mengawali penjelasannya. Sisa kata-katanya tidak pernah anda ketahui. Anda merasa dikelilingi kehampaan dan anehnya, rasa lega.

Setelah beberapa kali melihat siklus yang sama: datang penuh harap, mengikuti proses panjang nan berbelit-belit, yang pada akhirnya hanya untuk mendengar sebuah perkataan menusuk bertabur manisan yang terasa menyakitkan. Bahkan mendengar serpisahan keperihan dari kata rupawan yang menggelitik telinga itu harus disambut dengan suka cita. "Untung saja bukan kata: harap menunggu kabar dari kami", pikir anda. Setelah pukulan bertubi-tubi, yang anda harapkan hanya kejelasan. Setidaknya hari ini anda mendapatkannya.

Kaki anda lalu melangkah dari gedung tersebut dengan perasaan yang sama seperti sebelumnya. Menatap jauh dari hiruk pikuk keramaian jalan ibu kota dalam hati anda yang sepi. Meskipun ketegaran sudah anda dapatkan dari sekian kali siklus panjang yang menyebalkan, beberapa bagian dari lubuk hati anda tentu pernah terucap harapan yang berbeda dari yang anda dapatkan hari ini. Beberapa dari diri anda tentu berharap pencarian anda akan berakhir hari ini.

Bersamaan dengan langkah pelan yang anda ambil, hujan membasahi keramaian jalan malam. Anda dipaksa berlari mencari tempat teduh dengan timing terburuk yang bisa anda bayangkan. Namun, di saat tersebut, anda melihat rintikan hujan yang terhenti tepat di mata anda. Kendaraan juga tidak terlihat bergerak dan orang-orang di sekitar anda seolah mematung. Waktu seolah terhenti. Apa yang terjadi? Apa ini sebuah mimpi? Banyak pertanyaan lain yang lalu anda lempar hanya untuk tidak mendapatkan jawaban. Atau itulah yang sempat anda pikirkan.

" Ini adalah kenyataan. Bagaimana? Indah bukan?"

Sebuah suara menggema dari sebelah kanan anda. Dari wajah anda terlukis beberapa pertanyaan yang langsung mendapatkan jawaban dari sumber suara tersebut. Ia tersenyum, sebuah senyum yang tidak pernah anda lihat sebelumnya. Senyumannya kosong, tapi anda tidak merasakan ada niat jahat juga dibaliknya. Hanya saja, wajahnya tidak benar-benar memperlihatkan ekspresi layaknya manusia. Apakah ia manusia? Pikir anda. Apapun itu, ia pastinya bukan orang biasa.

" Anda itu apa?" Kata-kata itu adalah hal pertama yang keluar dari mulut anda. Dari raut wajah yang dingin dan hawa kehadiran yang mencekam. Jika anda menceritakan apa yang anda lihat ke orang lain, mungkin hal yang sama akan dilakukan. Namun, dia-yang-anda-tidak-tahu-apa tidak memberikan jawaban yang anda inginkan. Sebuah pertanyaan dijawab dengan pertanyaan lain.

" Anda sedang mencari pekerjaan bukan?" Makhluk itu berhenti tepat di depan anda dan mengeluarkan sepucuk kertas sebelum melanjutkan pembicaraannya. " Bekerja lah di perusahaan saya, di dunia lain" ajaknya dengan wajah sumringah.

Makluk itu terus berucap banyak hal setelahnya. Banyak hal yang tidak masuk akal. Dunia lain, sihir, dan dunia modern yang tidak duduk di sisi yang sama. Sementara makhluk yang ada di hadapan anda ini terus berceloteh mengenai hal tersebut. Banyak pertanyaan tentunya ingin anda berikan. Kenapa dia tahu anda sedang ingin mencari pekerjaan? Mengapa anda? Sihir, apakah hal seperti itu benar adanya? Banyak lagi pertanyaaan yang ingin anda lemparkan. Namun, tidak satu pun keluar dari mulut anda. Anda terus mendengarkan makhluk itu berbicara, yang meskipun tidak bisa anda pahami, anda tidak bisa tidak mendengarkan. Apakah itu karisma miliknya atau hanya anda tertahan oleh kekuatannya? Dua pertanyaan tersebut juga anda tidak tahu jawabannya.

" Bagaimana, anda pasti tertarik bukan?"

Entah karena terpengaruh kekuatannya, entah karena rasa putus asa yang anda miliki setelah beberapa kali ditolak, anda tanpa berpikir panjang menerimanya. Penerimaan anda disambut oleh wajah puas dari dia-yang-anda-tidak-tahu-apa.

" Ah benar. Saya belum memberikan nama. " Ia membungkukkan tubuhnya sebelum memberikan jawaban. "Saya adalah R̵͇̗̈̅̇̐̚e̸̪̯͖̠͍͊̂̋͗͝y̷̧̗͚͚͑͒̔̓a̸̩͕̎̏͌l̴̞̔̄͒̍̕l̵̡̬̱̹̇̈́̐͋͜͝e̵̲͆͒."

Telinga anda tidak dapat mendengar namanya. Seolah-olah sesuatu menghalangi anda mendengar namanya.

" Oh panggil saja saya 0. Nama saya tidak bisa didengar oleh sembarang orang. Nah, tanpa memperpanjang waktu, mari saya bawa anda ke dunia yang saya maksud."

Dengan begitu anda dikirim oleh R̵͇̗̈̅̇̐̚e̸̪̯͖̠͍͊̂̋͗͝y̷̧̗͚͚͑͒̔̓a̸̩͕̎̏͌l̴̞̔̄͒̍̕l̵̡̬̱̹̇̈́̐͋͜͝e̵̲͆͒ ke dunia lain. Ketika perjalanan dimulai anda merasakan keraguan yang tidak pernah anda rasakan sebelumnya. Keraguan yang tidak anda rasakan ketika belum menerima kontrak dari 0. Dengan langkah kaki goyah anda menatap sinis kebelakang, kepada diri anda yang beberapa menit yang lalu.

Hari Pertama yang Menyenangkan? Anda Pasti Bercanda

Anda duduk di sebuah ruangan setelah salah satu atasan baru anda selesai memperlihatkan tempat kerja anda yang baru. Tidak ada yanga aneh sebenarnya. Walau anda tahu tempat ini ada di dunia yang sama sekali berbeda dengan dunia asal anda, pada dasarnya tidak banyak perbedaan. Setidaknya perusahaan tempat anda bekerja dan distrik tempat anda tinggal saat ini.

Ketika mendengar kata "dunia lain", banyak hal terlintas di pikiran anda. Mungkin hal-hal tidak masuk akal seperti sihir, makhluk-makhluk aneh dapat anda temukan dengan mudah. Tentu saja bukan latar dunia modern dengan sebagian besar penduduknya merupakan seorang karyawan kantoran yang akan anda bayangkan. Keadaan yang anda lihat saat ini adalah kebalikan dari apa yang anda bayangkan akan anda temukan. Walau demikian anda merasakan kelegaan di dada anda. Setidaknya mungkin aku tidak perlu terlalu merubah cara hidupku. Begitu yang anda pikirkan.

"Maaf apa kamu lama menunggu?"

Anda menoleh ke sumber suara. Anda mendapati atasan anda berdiri dengan beberapa orang. Beberapa dari mereka mencoba tersenyum dan jujur terlihat seperti senyum yang dipaksakan. Setidaknya anda dapat mengapresiasi usaha mereka. Pada badge yang mereka kenakan, ada nama dan tulisan software engineer tertulis. Anda dapat membayangkan mereka adalah rekan kerja anda.

"Ini adalah anggota tim yang akan bekerja denganmu." kenal atasan anda. Atasan anda lalu memberikan beberapa lembar kertas sebelum berjalan kembali ke ruangannya. "Mereka akan menjelaskan pekerjaan divisi ini. Selamat bekerja."

Begitu saja dan batang hidung atasan anda tidak terlihat lagi, meninggalkan anda dengan kebingungan terlukis jelas pada ekspresi wajah anda. Sejak anda bertemu dengannya, ia selalu bersikap dingin. Ia hanya bicara sepatah-dua patah kata lalu diam. Anda lalu terpikirkan apakah dia kenal dengan 0. Jika iya, anda ingin menanyakan beberapa hal. Hanya saja 0 telah melarang anda membicarakan soal dia atau pun tentang anda yang berasal dari dunia lain. Kalau pun tidak ada larangan tersebut, anda ragu atasan anda akan mau menerima pertanyaan tersebut.

"Maafkan beliau. Dia memang tidak banyak bicara. " Salah satu dari rekan kerja anda sepertinya membaca ekspresi wajah anda.

"Ah tidak masalah." jawab anda sambil tertawa kecil untuk menyembunyikan rasa kesal yang anda rasakan.

"Namaku Rory. Rory Auden. Tolong panggil aku Rory. Di sana ada Emory, Riley, dan Alex. " Rory memperkenalkan satu per satu anggota tim baru anda. " Aku akan menjelaskan divisi apa ini. "

"Sub-divisi kita adalah tim yang fokus pada bagian backend. Ya walau karena sub-divsi frontend sering kekurangan orang, kita sering harus ikut serta mengurus pekerjaan mereka. " jelas Rory sambil tertawa tak nyaman. Anda langsung dapat menebak betapa kesalnya Rory setiap kali harus melakukan hal tersebut.

"Nah, oleh karena itu," Rory berhenti sebentar sambil membuka file di komputer miliknya. " Sebagai anggota baru divisi ini aku akan memintamu untuk membuat sebuah aplikasi secara full-stack untuk jaga-jaga. " lanjutnya.

Tugas Pertama

"Aku tidak akan membantumu dalam mengerjakan tugas ini. Namun, aku akan menjelaskan beberapa hal yang mungkin kamu perlu pahami."

Rory memperlihatkan berkas yang terlihat seperti requirement aplikasi yang ia minta buat. Aplikasi yang diminta pada dasarnya cukup sederhana, tapi ada satu masalah.

"Web framework ini.. Spring Boot?"

"Betul sekali. " jawab Rory membenarkan dugaan anda. "Aku akan memintamu untuk mencoba membuat aplikasi yang ada pada berkas itu menggunakan Spring Boot. Good luck".

"Tunggu!" tegas anda. Namun, Rory hanya membalasnya dengan ayunan tangan dan meninggalkan anda dalam kebingungan. Anda lalu hanya bisa menatap kertas tersebut dengan kepasrahaan.

"Apa kamu butuh bantuan?" Rekan kerja anda yang lain menghampiri anda.

"Ah, sedikit butuh. Aku tidak terbiasa menggunakan Spring Boot. Namamu tadi Emory? "

Emory mengangguk. "Aku bisa membantumu. Kamu biasa bekerja menggunakan apa?"

"Django, Laravel. Namun, aku paling percaya diri dengan kemampuan Django" jelas anda.

Di saat anda selesai menjelaskan kompetensi anda, Emory tiba-tiba menarik anda ke ruang kerjanya lalu dengan sigap mengunci pintu.

"Apa yang kamu lakukan?" Anda menatap bingung kepada Emory, hanya untuk disambut oleh wajah tanpa ekspresi yang terus ia perlihatkan sejak awal.

"Kamu datang dari dunia lain bukan?"

Sebuah pertanyaan dari Emory cukup membuat anda panik. 0 sudah memperingatkan anda untuk tidak mengatakan bahwa anda datang dari dunia lain. Walau begitu, walau anda sudah tahu, hanya dalam satu hari bekerja, rahasia anda sudah ketahuan.

"Harap tenang. Aku juga berasal dari dunia asalmu." Emory sepertinya melihat wajah panik anda dan berusaha menenangkan anda. "Orang dari dunia ini tidak akan tahu kamu dari dunia lain, tenang saja. Hanya saja, aku yang berasal dari dunia yang sama denganmu akan langsung menyadari rahasiamu dari caramu menyebut istilah asing dari bahasa dunia ini. Kekuatan penerjemahaan dari makhluk-yang-kita-tidak-tahu-apa itu tidak sempurna. Aku sangat menyarankanmu untuk belajar bahasa asli dunia ini. "

Mendengarkan penjelasan Emory anda tidak dapat bereaksi kecuali menganggukkan kepala anda. Orang dunia lain akan dapat mendeteksi orang lain berasal dari dunia tersebut dengan mudah. Itu mungkin adalah hal yang penting untuk anda pahami.

"Jika kamu sudah paham itu, mari aku bantu memahami beberapa hal mengenai Spring Boot dan mungkin version control, Git, yang kita gunakan di sini. Version control apa yang pernah anda gunakan di sana? " Emory kembali membawa anda ke meja kerja anda sambil menjelaskan beberapa hal.

"TFS" jawab anda singkat.

"Aku bersyukur kamu pernah bekerja dengan version control dan bukan menggunakan FOVC atau semacamnya?" sindir Emory.

"FOVC?"

"Flash-disk Oriented Version Controlling. Beberapa perusahaan gila juga menggunakan Google Drive™ untuk version control. Bersyukurlah kamu masih menggunakan TFS untuk pengalaman Version Control-mu. " pungkas Emory lagi.

Basic Spring Boot

Catatan: Harap menyiapkan semua environment sebelum melanjutkan ke bagian selanjutnya.

  1. IntelliJ Ultimate
  2. Git

Beberapa tutorial pendukung juga tersedia pada SCeLe. Harap perhatikan tutorial di bawah dibuat pada IntelliJ Idea Ultimate.

Membuat Proyek Spring Boot

"Coba buka Spring Initializr pada menu new project IDE-mu. Isikan apa yang aku isikan ini " Emory memperlihatkan beberapa konfigurasi yang ia gunakan untuk membuat Spring Project. "Tentu saja, untuk location silahkan pilih sesukamu.Tolong gunakan project SDK dengan versi yang lebih tinggi atau setidaknya sama dengan versi Java yang dipilih. Selain menggunakan IDE Anda juga dapat membuat proyek ini dengan langsung mengakses Spring Initializr "

"Struktur aplikasi yang akan kamu dapatkan akan seperti ini. " Emory memperlihatkan struktur aplikasi yang didapatkan dari proses pembuatan proyek dari Spring Initializr. " Aku akan menjelaskan beberapa folder yang perlu dipahami."

  1. Folder src memiliki dua sub-folder penting, yaitu main dan test. Seperti namanya test akan berisikan kode sumber untuk test yang akan kamu buat. Sementara main berisikan kode aplikasi. Folder main sendiri berisikan dua folder lain, java (berisikan kode java) dan resources berisikan aset pendukung aplikasi web, seperti file static, templates (HTML), dan konfigurasi aplikasi dalam application.properties. Beberapa kofigurasi seperti basis data yang kamu kenal di Django berada pada settings.py. Pada Spring, konfigurasi tersebut ada pada application.properties ini.
  2. gitignore, berisikan file-file yang tidak ingin di-commit pada version control nantinya.
  3. File-file Gradle. Ada empat file penting: build.gradle, gradlew, gradlew.bat dan settings.gradle. Kamu mungkin tidak akan perlu mengotak-atik gradlew dan gradlew.bat jadi kita fokuskan pada build.gradle dan settings.gradle

  4. File build.gradle berisikan Gradle task, informasi aplikasi, konfigurasi, plugin, dan dependencies. Versi Java yang digunakan juga dapat diatur pada file ini. Jika pada Python kamu mungkin terbiasa meletakkan dependencies pada requirements.txt atau Pipfile, Spring Boot atau pun aplikasi berbasis Gradle secara umum meletakkan dependencies pada build.gradle.

  5. File settings.gradle adalah dimana pengaturan module gradle tersebut. Pada kasus ini, kamu dapat melihat nama dari module tersebut. Pada penggunaan lebih lanjut, misalkan pada pengaturan Gradle Multimodule, kamu akan menggunakan file ini untuk mengaktifkan sebuah sub-module.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
plugins {
    id 'org.springframework.boot' version '2.6.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'id.ac.ui'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

Dasar Model-View-Controller pada Spring Boot

"Apakah kamu familiar dengan konsep design pattern Model-View-Controller atau mungkin lebih sering disingkat sebagai MVC?" Emory melanjutkan penjelasan pada desain struktur dan peran desain tersebut pada aplikasi.

" Aku pernah bekerja dengan MVT pada Django. " jawab anda.

Emory menampilkan wajah yang sedikit lega. "Kalau begitu seharusnya penjelasan ini tidak akan panjang. " timpalnya.

"Model-View-Template atau MVT pada Django pada dasarnya adalah turunan/derivative dari MVC. Design pattern ini dibuat dan disesuaikan untuk penggunaan pada Django. Perbedaannya adalah ada pada dua nama yang sama, tapi memiliki peran berbeda. View pada Django merupakan logika bisnis dan juga mengatur request dan response aplikasi. Sementara View pada MVC merupakan lapisan yang bertugas untuk tampilan. Pada Django, ini adalah tugas Template. Jika disederhanakan, View adalah Controller pada MVC dan Template adalah View pada MVC. Yah ini adalah penjelasan yang sangat disederhanakan. Selanjutnya, aku akan menjelaskan tugas tiap layer."

  1. Model adalah layer yang merepresentasikan data dan juga logika bisnis.
  2. Controller adalah lapisan yang mengatur request dan response dari aplikasi. Idealnya, tidak ada program yang mengatur logika bisnis pada lapisan ini. Fokusnya adalah mengelola request dari pengguna dan mengembalikan response yang sesuai.
  3. View adalah tampilan. Sederhananya pada aplikasi web adalah ini adalah file kerja sama antara file HTML dan static file seperti CSS dan JavaScript. Pada kenyataannya, ada kerja sama antara lapisan ini dengan Controller untuk dapat bekerja dengan semestinya.

Pada Spring Boot sendiri ada sedikit penambahan lapisan untuk meningkatkan maintainability dan juga memperjelas peran tiap lapisan. Dapat dilihat bahwa lapisan Model memiliki peran yang cukup luas. Agar tiap lapisan memiliki satu tugas khusus yang jelas, Model dibagi lagi menjadi dua lapisan: 1. Service Lapisan yang khusus mengurus logika bisnis. 2. Repository Lapisan yang khusus mengurus pengelolaan data.

Dengan demikian, Model hanya akan merepresentasikan data.

Dasar Request dan Response pada Spring Boot

"Selanjutnya ayo kita coba buat sebuah aplikasi web sederhana." Emory mulai mengetikkan beberapa hal pada IDE miliknya. "Coba buat susunan package seperti ini. Setiap package ini akan merepresentasikan sebuah lapisan pada MVC Spring Boot. "

"Untuk langkah pertama mari kita mulai dari membuat Controller. Sebuah Controller di Spring Boot adalah kelas yang diberikan annotasi @Controller. Coba buat sebuah Controller sederhana yang dapat mengembalikan halaman HTML terlebih dahulu. "

Buat Kelas BaseController pada package controller. Isi seperti berikut. Coba untuk tidak melakukan menuliskan import statement secara manual.

"Kelas BaseController dalam keadaan sekarang sudah dianggap menjadi sebuah Controller oleh Spring Boot. Sebuah kelas Controller biasanya memiliki beberapa method yang setiap method ini akan merepresentasikan URL tertentu. Contohnya adalah method index pada kelas BaseController. Method tersebut memiliki annotasi @RequestMapping. Ini adalah annotasi yang menandakan sebuah method akan menangani sebuah endpoint. Ada paramater yang perlu diperhatikan, method dan path. Parameter method merupakan HTTP Method apa yang diterima oleh method tersebut, dalam hal ini GET. Parameter path di sisi lain merupakan URL apa yang akan ditangani oleh method tersebut. Nama method sebenarnya tidak ada pengaruhnya pada apapun, tapi tetap usahakan berikan nama yang sesuai dengan apa yang ditangani oleh method tersebut. Terakhir adalah return type dari sebuah controller method. Semuanya mengembalikan string. Nah, string yang dikembalikan ini adalah nama dari html yang ada pada folder resources/template. Dapat dilihat bahwa di sini method tersebut mengembalikan home, sementara di folder template belum ada file home.html. Karena belum ada, kita harus membuatnya."

Buat home.html pada resources/template

Isikan seperti berikut.

Sekarang coba jalan aplikasinya. Buka Tutorial0Application, klik lambang run.

Jika tidak ada masalah, maka pada command line IDE akan terlihat sebagai berikut

Hasilnya sebagai berikut:

" Sekarang coba tambahkan dua method lagi pada BaseController"

Emory menggarisbawahi annotasi @GetMapping pada kedua method contoller tersebut. Anda menyadari ada sedikit perbedaan dengan @RequestMapping yang digunakan sebelumnya pada method index.

"Apa kamu menyadari sesuatu?"

Anda menggangguk. "Apakah GetMapping merupakan RequestMapping yang memiliki HTTP method POST?"

Seperti biasa, Emory hanya mengiyakan dengan gestur tubuhnya. "Dengan ini penulisan bisa lebih concise. Selain itu, ada kegunaan lain dari RequestMapping, URL Groupping. Ini akan memungkinkan kamu untuk mengelompokkan URL yang memiliki prefix yang sama, seperti /student/create dan /student/list Kamu akan perlu menggunakan ini untuk membuat aplikasi yang diminta tadi."

Pada dua method tersebut ada dua annotasi lain dan juga sebuah parameter model pada argumen dari method tersebut.

"Selanjutnya mari kita menggunakan RequestParam dan PathVariable. Kedua parameter ini penting untuk mengendalikan URL mapping. Mungkin akan lebih mudah untuk langsung memperlihatkan contoh penggunaannya saja. Karena ada method ini, ada sedikit perubahan yang perlu dilakukan pada HTML nya. Coba ubah menjadi seperti berikut!"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>

<h3 th:if="${name!=null}" th:text="${'Welcome home, ' + name}"></h3>
<h3 th:unless="${name!=null}">Welcome</h3>



</body>
</html>

"th:if dan th:unless merupakan statement if dan else untuk template engine Thymeleaf. Karena kamu pernah menggunakan Django, aku yakin kamu pernah menggunakan hal semacam ini bukan? Di sini dapat dilihat bahwa pengecekan dilakukan terhadap variabel name. Dapat kamu lihat ini adalah variabel yang dimasukkan oleh parameter model. Perhatikan model.addAttribute("name", name); model pada dasarnya merupakan sebuah pasangan key-value. name yang dipanggil pada HTML adalah key dan value nya adalah object Java yang kebetulan punya nama name juga.Kamu mungkin biasanya menggunakan sebuah dictionary pada Django untuk keperluan semacam ini bukan? Jadi aku rasa kamu bisa asumsikan model di sini adalah equivalent dictionary pada Spring Boot. Jadi harap perhatikan model di sini bukan Model dari M pada MVC."

Sekarang coba jalankan dan buka http://localhost:8080/greet?name=Emory, harusnya didapatkan hasil sebagai berikut:

Anda bisa menggantikan Emory dengan apapun yang anda inginkan, coba ganti Emory dengan nama anda sendiri dan lihat hasilnya.

" Coba bandingkan dengan yang ini " Anda diminta membuka halaman http://localhost:8080/greet/Emory

"Apa perbedaannya? " tanya Emory.

Anda dapat melihat ada perbedaan pada url pattern yang digunakan.

"Aku akan mencoba menjawab berdasarkan observasiku " jawab anda mengawali jawaban anda. "Aku kasus pertama adalah penggunaan RequestParam, ditandai dengan ?namaParam atau dalam kasus ini adalah ?name. Kasus kedua adalah PathVariable ditandai dengan /greet/{variableName} dan lagi-lagi pada kasus ini adalah name"

Emory tidak mengatakan apapun, tapi wajahnya terlihat puas. Anda menganggap reaksi tersebut sebagai tanda jawaban anda benar.

"Sampai saat ini kamu sudah menyelesaikan suatu fungsi dasar pada Spring Boot. Pada saat ini, sebaiknya kamu mulai commit progress yang sudah dilakukan pada Git"

Saat ini folder proyek Spring dari Spring Initializr belum dinyatakan sebagai folder git. Untuk membuat sebuah folder menjadi gunakan command:

1
git init

Silahkan cek, ada perubahan apa dalam folder git (dalam hal ini file-file apa yang belum di-add di luar file yang ada dalam gitignore) dengan menggunakan

1
git status

Untuk melakukan add file ke git, gunakan

1
git add [Nama file]

Jika kamu ingin langsung menambahkan semua file ke git bisa melakukan, tapi pastikan semua file yang ada pada git status memang file yang ingin ditambahkan. Best practice-nya adalah lakukan add dan commit dengan perubahan seminimal mungkin. Kamu akan mempelajari cara commit seperti ini nantinya.

1
git add .

Kamu bisa menggunakan git status lagi untuk mengecek file apa saja yang telah di-add.

Selanjutnya lakukan commit dengan perintah:

1
git commit -m "[commit message]"

Untuk saat ini lakukan commit dengan pesan Initial commit

1
git commit -m "Initial commit"

Implementasi Tugas Pertama

Emory menatap berkas yang Rory berikan kepada anda. Wajahnya terlihat tidak tertarik, mungkin terlihat bosan. Setelah beberapa detik membolak-balikkan kertas tersebut ia mengembalikannya pada anda.

"Orang itu selalu memberikan tugas yang sama kepada setiap rekrutan baru" keluhnya.

"Baiklah. Seperti yang kamu lihat, aplikasi ini adalah prototipe aplikasi sistem informasi asisten. Aplikasi ini adalah aplikasi yang dibuat untuk sebuah universitas kecil di pinggiran kota Prediff. "

"Prediff adalah kota liar di barat kota perdagangan Yukginia bukan?" Tanya anda. Emory hanya mengangguk lirih.

"Walau aku bilang begitu, aplikasi yang diminta kepada kamu hanya versi sederhananya. Untungnya, anda juga sudah diberikan requirements detailnya." Anda diminta membuat sebuah halaman yang dapat membuat Student baru dan menampilkannya di halaman lain. Begitu juga untuk Course. Aku akan membantumu membuat halaman Student, tapi aku ingin kamu membuat Course sendiri. Kebetulan hanya aku hanya dapat membantumu sampai di sini."

"Terima kasih banyak" Anda sangat bersyukur Emory bersedia membantu anda menyelesaikan tugas anda. Setidaknya di antara beberapa orang, anda memiliki rekan senior yang dapat membantu anda.

1
2
3
4
5
6
7
8
9
[Student]
name - character - No students allow to have same name
NPM - character, auto-generated - ASCII value of each student' name
address - character

[Course]
courseId - auto-generated - ASCII value of each course' name
course name - character
vacancy status - boolean default true

Pertama, kita harus mengubah requirements tersebut menjadi kode. Salah satu langkah paling mudah adalah membuat model atau core dari aplikasi ini terlebih dahulu. Buat class Student pada package model. Namun, sebelum itu mari membuat branch baru pada git.

"Alasan pertama mengapa kita membuat branch adalah seperti yang aku bilang tadi adalah changes yang kita buat harus fokus dan minimal. Selain itu, agar long-lived branch seperti master hanya digunakan oleh kode yang benar-benar siap untuk dimasuki. Mungkin kamu dapat mencari kegunaan lain dari branch. Aku serius, aku akan menanyakan hal ini nantinya. "

Buat branch dengan nama task-implementation-student dengan perintah:

1
git checkout -b [branchName]

Atau dalam kasus ini berarti

1
git checkout -b task-implementation-student

Setelah ini, mulai bekerja dengan membuat model sebagai berikut:

Lakukan generate setter dan getter. Pada IntelliJ, dapat menggunakan klik kanan -> generate -> Setter and Getter atau menggunakan shorcutalt+insert (pada Linux dan Windows). Silahkan cek menggunakan klik kanan untuk melihat shorcut pada mesin anda.

"Nah kebetulan, untuk mengurangi boiler code kita akan menggunakan Lombok. Jika kamu ingat itu termasuk dependencies yang aku minta masukkan di awal tadi. Sekarang hapus setter dan getter yang sudah kamu buat, lalu ganti dengan kode di bawah."

Setelah selesai langkah selanjutnya adalah membuat lapisan Repository.

" Yang aku ajarkan adalah versi yang disederhanakan dari model dan repository. Pada kenyataannya, kamu pasti ingin membuat kedua lapisan ini yang terintegrasi dengan basis data. Sementara mari kita buat 'basis data' sederhana menggunakan struktur data. "

Kelas ini pada dasarnya adalah kelas Java biasa. Kita belum mencoba menggunakan fitur Repository yang terhubung langsung ke basis data dari Spring Boot. Hal utama yang perlu dipahami ada pada annotasi @Repository. Sederhananya annotasi mirip seperti @Controller, berfungsi untuk menandakan bahwa sebuah Kelas adalah Repository. Dengan menandakan bahwa kelas ini adalah repository, kelas tersebut telah menjadi sebuah Bean yang akan sangat kita gunakan pada Spring Boot.

Selanjutnya buat Service dari Student. Pertama buat interface StudentService pada package service, isikan sebagai berikut:

"Nah, ingat tadi ada beberapa hal yang perlu dicek pada proses bisnis Student? Ada keperluan untuk validasi. Mari kita buat sebuah Exception untuk kasus ada input nama Student yang duplikat. Buat package baru bernama exception dan buat kelas DuplicateStudentNameException"

1
2
3
4
5
6
7
8
package id.ac.ui.tutorial0.exception;

public class DuplicateStudentNameException extends RuntimeException {

    public DuplicateStudentNameException(String studentName) {
        super(String.format("The student name %s is a duplicate!",studentName));
    }
}

Karena persiapan selesai, selanjutnya buat implementasi dari interface StudentService tadi. Buat kelas StudentServiceImpl pada package Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package id.ac.ui.tutorial0.service;

import id.ac.ui.tutorial0.exception.DuplicateStudentNameException;
import id.ac.ui.tutorial0.model.Student;
import id.ac.ui.tutorial0.repository.StudentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private StudentRepository studentRepository;

    @Override
    public Student create(Student student) {
        validateName(student);
        generateNPM(student);
        studentRepository.create(student);
        return student;
    }

    private void validateName(Student student) {
        List<Student> allStudents = findAll();
        for(Student dbStudent: allStudents) {
            if(dbStudent.getName().equals(student.getName())) {
                throw new DuplicateStudentNameException(student.getName());
            }
        }

    }

    private void generateNPM(Student student) {
        StringBuilder stringBuilder = new StringBuilder();
        for (char letter: student.getName().toCharArray()) {
            stringBuilder.append(String.valueOf((int)letter));
        }
        String npm = stringBuilder.toString();
        student.setNpm(npm);
    }

    @Override
    public List<Student> findAll() {
        Iterator<Student> studentIterator = studentRepository.findAll();
        List<Student> allStudents = new ArrayList<>();
        studentIterator.forEachRemaining(allStudents::add);
        return allStudents;
    }
}

" Perhatikan @Service dan @Autowired. Aku yakin pada saat ini kamu sudah bisa menebak apa itu @Service. Mari kita fokus pada @Autowired"

@Autowired merupakan sebuah mekanisme dependency injection pada Spring Boot. Dalam hal ini dapat dilihat bahwa kita tidak secara eksplisit menuliskan inisialisasi dari StudentRepository. @AutoWired membuat Spring dapat membuat sendiri instance dari StudentRepository. Hal ini dimungkinkan hanya pada sebuah Bean. Ingat sebelumnya kita telah memberikan annotasi Repository pada StudentRepository. Dengan demikian StudentRepository adalah sebuah bean dan bisa menggunakan @Autowired untuk proses inisialisasi. Anda akan melihat hal yang sama untuk StudentService pada Controller nantinya.

Sekarang, buat implementasi dari Kelas StudentController pada Package Controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package id.ac.ui.tutorial0.controller;

import id.ac.ui.tutorial0.exception.DuplicateStudentNameException;
import id.ac.ui.tutorial0.model.Student;
import id.ac.ui.tutorial0.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/student")
public class StudentController {

    @Autowired
    private StudentService service;

    @GetMapping("/list")
    public String studentListPage(Model model) {
        List<Student> allStudents = service.findAll();
        model.addAttribute("students", allStudents);
        return "studentList";
    }

    @GetMapping("/create")
    public String createStudentPage(Model model) {
        Student student = new Student();
        model.addAttribute("student", student);
        return "createStudent";
    }

    @PostMapping("/create")
    public String createStudentPost(@ModelAttribute Student student, Model model) {
        try {
            service.create(student);
        } catch (DuplicateStudentNameException e) {
            model.addAttribute("error", e);
            model.addAttribute("student", student);
            return "createStudent";
        }
        return "redirect:list";
    }

}

"Aku tidak akan menjelaskan semuanya lagi. Aku cukup yakin kamu telah familiar dengan beberapa hal pada kode ini. Fokus kita adalah form untuk membuat student. Perhatikan method createStudentPage dan createStudentPost. Namun, sebelum itu mari kita implementasi studentList.html dan createStudent.html"

studentList.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Student List</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>

<h2>Student' List</h2>
<a th:href="@{/student/create}" class="btn btn-primary btn-sm mb-3" >Create student</a>

<table class="table">
  <thead>
  <tr>
    <th scope="col">Name</th>
    <th scope="col">NPM</th>
    <th scope="col">Address</th>
  </tr>
  </thead>
  <tbody th:each="student: ${students}">
  <tr>
    <td th:text="${student.name}"></td>
    <td th:text="${student.npm}"></td>
    <td th:text="${student.address}"></td>
  </tr>

  </tbody>
</table>


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>

createStudent.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Create new Student</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
<h3>Create new Student</h3>
<div th:if="${error!=null}" class="alert alert-danger" role="alert">
    Duplicate student name
</div>
<form th:action="@{/student/create}" th:object="${student}" method="post">
    <div class="form-group">
        <label for="nameInput">Name</label>
        <input th:field="*{name}" type="text" class="form-control" id="nameInput" aria-describedby="nameHelp" placeholder="Enter student' name">
        <small id="nameHelp" class="form-text text-muted">Please enter unique name.</small>
    </div>
    <div class="form-group">
        <label for="addressTextarea">Address</label>
        <textarea th:field="*{address}" class="form-control" id="addressTextarea" rows="3"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>

Proses pembuatan Student dibagi menjadi dua, direpresentasikan oleh 2 method tadi. Method createStudentPage yang memiliki HTTP Method - Get, merupakan method yang bertugas untuk mengarahkan pengguna ke halaman form untuk membuat Student. Sementara createStudentPost yang memiliki HTTP Method - Post, merupakan method yang bertugas untuk submit form ke backend.

Proses submit form yang terdiri dari dua bagian ini juga perlu diperhatikan. Perhatikan method createStudentPage!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Student student = new Student();
model.addAttribute("student", student);
 ```
Terlihat bahwa pada method tersebut ada pembuatan object `Student` baru dan mengirimkan object tersebut ke form. Pada HTML, object itu akan di-bind ke
input fields yang ada. Bagian deklrasi form berikut memperlihatkan bahwa objek tersebut dipasangkan ke form`<form th:action="@{/student/create}" th:object="${student}" method="post">` Sementara proses binding field terlihat pada potongan kode berikut: `th:field="*{name}"`. Hal ini memperlihatkan bahwa bagian input field tersebut dipasangkan dengan field `name` pada `Student`.

Annotasi `@ModelAttribute` memberi tahu handler form tersebut, dalam kasus ini `createStudentPost` bahwa objek tersebut yang akan dikirimkan ke backend. Proses validasi sesuai bisnis proses diserahkan kepada lapisan Service, tapi Controller tetap memiliki tugas untuk mengembalikan respon yang sesuai dari apa yang dikembalikan oleh Service. Dalam kasus ini, klausa `try-catch` merupakan handling dari respon tersebut (ingat pada Service tadi kita melakukan `throw` sebuah Exception ).
Jika tidak ada masalah pada input yang diberikan, user akan di-redirect kembali ke halaman `/student/list`. Apabila ada masalah, user akan dikembalikan ke halaman form tersebut dengan juga menyertakan objek `error` yang akan di-render pada bagian berikut pada HTML

```html
<div th:if="${error!=null}" class="alert alert-danger" role="alert">
    Duplicate student name
</div>

"Sekarang kembali lakukan add dan commit. Namun, sekarang mari manfaatkan fitur yang tersedia pada IDE"

Buka menu git pada IntelliJ dan buka commit. Windows berikut akan muncul.

Anda dapat melihat bahwa daftar file tersebut adalah daftar file yang sama dengan apa yang ada pada git status. Tampilan change pada IDE anda mungkin berbeda. Anda mungkin akan mendapati file-file yang anda buat belum di-add. Pada kasus tersebut, silahkan cek pada bagian Unversioned Files. Pilih file-file terkait dengan implementasi Student yang sudah kita implementasikan dan kita akan menuliskan pesan commit.

Tulis Implement student page pada text box commit message, lalu klik Commit. (Terkadang akan ada warning, untuk latihan ini, lanjutkan saja dengan Commit anyway jika ada prompt).

Ada beberapa aturan dalam penulisan commit message yang perlu diikuti. Coba baca artikel berikut, How to Write Git Commit Message, untuk mempelajari lebih lanjut mengenai hal tersebut.

Some Advanced Git

Emory melihat program anda beberapa saat.

"Oke, terlihat tidak masalah" komentarnya. "Sekarang coba push pekerjaan anda ke repository. Oh jangan lupa push pekerjaan pada master branch terlebih dahulu.Aku mengasumsikan anda paham cara membuat repository pada Gitlab dan melakukan konfigurasi git pada local (hint: git remote). Berikan nama yang deskriptif."

Pindah ke branch master. Anda dapat menggunakan GUI melalui IntelliJ atau menggunakan command line.

1
git checkout master

Push pekerjaan anda. Sekali lagi anda dapat menggunakan GUI melalui menu Git pada IntelliJ atau git push.

Pindah kembali ke branch task-implementation-student, lakukan push. Pelajari mengenai Merge Request. Buatlah Merge Request ke branch master dari branch task-implementation-student. Coba merge dan amati apa yang terjadi. Anda akan diminta menjelaskan ini nantinya.

"Sekarang kita akan coba melakukan simulasi penggunaan git reset dan git revert"

Git Reset

Buat lah kelas MockStudentServiceImpl pada package Service, isikan sebagai berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package id.ac.ui.tutorial0.service;


import id.ac.ui.tutorial0.model.Student;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class MockStudentServiceImpl implements StudentService{

    @Override
    public Student create(Student student) {
        return null;
    }

    @Override
    public List<Student> findAll() {
        return null;
    }
}
Lakukan git add dan commit dengan pesan yang deskriptif terhadap perubahan tersebut. Coba jalankan aplikasi anda dan anda akan mendapati aplikasi anda tidak berjalan lagi. Hal ini diakibatkan oleh ada dua bean dengan tipe StudentService. Hal ini membuat Spring tidak tahu bean mana yang harus digunakan. Anda dapat memperbaiki masalah ini dengan mengganti @Autowired pada StudentController dengan @Qualifer dan secara eksplisit memilih StudentServiceImpl. Namun, untuk tutorial kali ini, kita akan mencoba menyelesaikan masalah tersebut dengan mengembalikan versi commit kita sebelumnya menggunakan git reset. Anda dapat mempelajari lebih dalam mengenai Qualifer untuk memahami bagaimana menangani kasus jika ada dua bean dengan tipe yang sama.

Seperti biasa anda dapat menggunakan fitur IDE anda untuk melakukan untuk melakukan hal ini, tapi sebaiknya coba pelajari cara menggunakan command line terlebih dahulu. (Hint: silahkan coba menu git pada pojok kiri bawah IDE anda untuk bereksplorasi fitur-fitur git pada IDE anda)

Jalankan perintah berikut untuk melihat ada commit apa saja yang telah anda lakukan:

1
git log

Tampilan command line

Tampilan IDE

Kita akan coba menyelesaikannya dengan pendekatan command line. Untuk pendekatan IDE, silahkan dicoba sendiri.

Anda dapat lihat aplikasi anda masih dapat bekerja dengan baik pada commit Implement Student Page.

Sekarang mari lakukan git reset untuk kembali pada commit tersebut. Copy hash commit dari commit Implement Student Page . Perhatikan: hash commit yang anda miliki akan berbeda dengan contoh.

Perintah git reset pada dasarnya adalah menggunakan sintaks berikut:

1
git reset --hard <hash commit>

Contoh (Sekali lagi ini contoh. Hash commit anda akan berbeda. Jangan lakukan copy paste contoh berikut. Copy hash dari tampilan git log anda sendiri)

1
git reset --hard 6c9932f317b4763619e0336cf9adf77abeb1666d

Ketika anda mencoba membuka git log anda akan mendapati commit dari MockStudentService telah hilang. Coba jalankan aplikasi anda kembali dan pastikan aplikasi anda sudah kembali berjalan. (Coba pelajari apa yang dilakukan oleh argument --hard. Apa ada opsi lain?)

Git Revert

Buat branch baru bernama simulation-revert. Kembali lakukan penambahan MockStudentServiceImpl seperti bagian sebelumnya. Setelah melakukan git add dan git commit lanjutkan dengan git push.

Coba simulasikan git reset kembali. Coba lakukan push. Anda akan mendapati bahwa push tersebut ditolak oleh git. Hal ini dikarenakan remote repository anda memiliki commit ahead local repository anda. Anda tidak dapat melakukan push kecuali menggunakan git push --force (For The Love of God, Don't try this at home.)

Dalam keadaan seperti ini, anda akan lebih cocok menggunakan git revert. Pertama lakukan, git pull untuk menarik kembali perubahan yang anda lakukan pada remote repository anda. Buka kembali git log anda. Berbeda dengan git reset yang kita lakukan bukan kembali ke commit dimana masalah ini belum ada, tapi membatalkan perubahan commit yang bermasalah. Dengan demikian, copy hash commit implementasi MockStudentService anda. Jalankan:

1
git revert <commit>

Contoh

1
git revert bf982a888db6db38af6d2e4b663f5e3495179ceb

Anda akan diminta melakukan commit. Isikan sesuai dengan deskripsi yang baik

Hasilnya akan terlihat sebagai berikut:

Terlihat bahwa proses revert akan menambahkan commit baru yang membatalkan commit sebelumnya. Lakukan push dan kembali ke branch task-implementation-student

Akhir dari Hari Pertama

"Bagaimana?" tanya Emory. Ia terlihat baru saja menyelesaikan task-nya hari ini. Mata dinginnya berbanding terbalik dengan perhatian yang ia berikan. Anda yang masih berusaha menyelesaikan task tersisa, melakukan implementasi halaman untuk Course.

"Tidak buruk." komentar anda.

"Jika begitu, semoga beruntung" Emory tidak terlihat tertarik. Ia lalu mengemasi barangnya dan bersiap untuk kembali ke rumahnya, dugaan anda. "Jangan lupa apa yang aku ajarkan. Rory dan bos mungkin akan mengevaluasi semuanya, dari kualitas kode hingga disiplin git. "

Emory lalu berjalan ke arah meja kerja anda, menarik sebuah kursi, dan duduk di samping anda. Anda menatap bingung ke arah Emory.

"Aku akan istirahat di sini sebentar. Jika ada pertanyaan, silahkan ganggu aku." ujarnya.

"Ah terima kasih."

"Cepat selesaikan. Aku akan membawamu keliling kota ini." lanjut Emory seolah tidak ada eksperesi.Tindakannya bertolak belakang dengan nada suaranya yang dingin. "Sudah lama aku tidak melihat orang dari duniaku. Aku rasa aku ingin tahu seperti apa di sana akhir-akhir ini. " ujarnya lagi.

"Baik." jawab anda singkat.

Hari pertama anda diakhiri dengan perjalan singkat ke beberapa tempat bersama senior dingin anda. Wajahnya terus tidak menampakkan eksperi, walau anda dapat merasakan mood-nya yang terlihat lebih ceria dari pertama kali anda bertemu dengannya. Anda tersenyum. Setidaknya bukan cara yang buruk untuk mengakhiri hari pertama anda. Di bawah rembulan kemudian anda terus berbincang dengan Emory. Tentang dunia ini, tentang 0, dan beberapa orang lain dari dunia anda. Jika bisa anda ingin berkenalan dengan mereka. Melihat kisah mereka. Apakah mereka datang tanpa penyesalan atau sebaliknya, jika ada cara untuk kembali, apakah mereka memilih untuk kembali? Di sinilah berawal kisah anda dan mereka yang dikirim untuk bekerja di dunia lain.

Task

  • Membaca Tutorial ini
  • Menyelesaikan setiap langkah yang diminta pada Tutorial
  • Menyelesaikan implementasi Course sesuai dengan requirements

Hint

  • Anda dapat mempelajari pola dari proses implementasi Student untuk implementasi Course
  • Pastikan anda tetap mengikuti disiplin git yang baik untuk menyelesaikan Course
  • Pastikan anda mempelajari bagian yang meminta anda mempelajari sesuatu. Anda akan diminta menjelaskan hal tersebut saat sesi demo nantinya.

Last update: 2022-02-08 21:14:01
Created: 2022-02-08 21:14:01
Back to top