5 Hal yang Belum Pernah Anda Lakukan dengan Spesifikasi REST
Diterbitkan: 2022-03-11Sebagian besar pengembang front-end dan back-end telah berurusan dengan spesifikasi REST dan RESTful API sebelumnya. Tetapi tidak semua RESTful API dibuat sama. Faktanya, mereka jarang RESTful sama sekali…
Apa itu RESTful API?
Ini adalah mitos.
Jika Anda berpikir bahwa proyek Anda memiliki RESTful API, kemungkinan besar Anda salah. Ide di balik RESTful API adalah untuk mengembangkan dengan cara yang mengikuti semua aturan arsitektur dan batasan yang dijelaskan dalam spesifikasi REST. Namun, secara realistis, ini sebagian besar tidak mungkin dalam praktiknya.
Di satu sisi, REST mengandung terlalu banyak definisi yang kabur dan ambigu. Misalnya, dalam praktiknya, beberapa istilah dari metode HTTP dan kamus kode status digunakan bertentangan dengan tujuan yang dimaksudkan, atau tidak digunakan sama sekali.
Di sisi lain, pengembangan REST menciptakan terlalu banyak batasan. Misalnya, penggunaan sumber daya atom tidak optimal untuk API dunia nyata yang digunakan dalam aplikasi seluler. Penolakan penuh penyimpanan data antara permintaan pada dasarnya melarang mekanisme "sesi pengguna" yang terlihat di mana-mana.
Tapi tunggu, itu tidak seburuk itu!
Untuk Apa Anda Membutuhkan Spesifikasi REST API?
Terlepas dari kekurangan ini, dengan pendekatan yang masuk akal, REST masih merupakan konsep yang luar biasa untuk membuat API yang sangat hebat. API ini dapat konsisten dan memiliki struktur yang jelas, dokumentasi yang baik, dan cakupan pengujian unit yang tinggi. Anda dapat mencapai semua ini dengan spesifikasi API berkualitas tinggi .
Biasanya spesifikasi REST API diasosiasikan dengan dokumentasinya . Tidak seperti spesifikasi—deskripsi formal API Anda—dokumentasi dimaksudkan agar dapat dibaca manusia: misalnya, dibaca oleh pengembang aplikasi seluler atau web yang menggunakan API Anda.
Deskripsi API yang benar bukan hanya tentang menulis dokumentasi API dengan baik. Dalam artikel ini saya ingin berbagi contoh bagaimana Anda dapat:
- Jadikan pengujian unit Anda lebih sederhana dan lebih andal;
- Siapkan prapemrosesan dan validasi input pengguna;
- Otomatisasi serialisasi dan pastikan konsistensi respons; dan bahkan
- Nikmati manfaat pengetikan statis.
Tapi pertama-tama, mari kita mulai dengan pengenalan dunia spesifikasi API.
API Terbuka
OpenAPI saat ini merupakan format yang paling banyak diterima untuk spesifikasi REST API. Spesifikasi ditulis dalam satu file dalam format JSON atau YAML yang terdiri dari tiga bagian:
- Header dengan nama, deskripsi, dan versi API, serta informasi tambahan apa pun.
- Deskripsi semua sumber daya, termasuk pengidentifikasi, metode HTTP, semua parameter input, kode respons, dan tipe data isi, dengan tautan ke definisi.
- Semua definisi yang dapat digunakan untuk input atau output, dalam format Skema JSON (yang, ya, juga dapat direpresentasikan dalam YAML.)
Struktur OpenAPI memiliki dua kelemahan signifikan: Terlalu rumit dan terkadang berlebihan. Sebuah proyek kecil dapat memiliki spesifikasi JSON ribuan baris. Mempertahankan file ini secara manual menjadi tidak mungkin. Ini adalah ancaman signifikan terhadap gagasan untuk menjaga spesifikasi tetap mutakhir saat API sedang dikembangkan.
Ada beberapa editor yang memungkinkan Anda mendeskripsikan API dan menghasilkan output OpenAPI. Layanan tambahan dan solusi cloud berdasarkan mereka termasuk Swagger, Apiary, Stoplight, Restlet, dan banyak lainnya.
Namun, layanan ini tidak nyaman bagi saya karena kerumitan pengeditan spesifikasi cepat dan menyelaraskannya dengan perubahan kode. Selain itu, daftar fitur tergantung pada layanan tertentu. Misalnya, membuat pengujian unit lengkap berdasarkan alat layanan cloud hampir tidak mungkin. Pembuatan kode dan titik akhir yang mengejek, meskipun tampaknya praktis, ternyata sebagian besar tidak berguna dalam praktik. Ini sebagian besar karena perilaku titik akhir biasanya bergantung pada berbagai hal seperti izin pengguna dan parameter input, yang mungkin terlihat jelas bagi arsitek API tetapi tidak mudah dibuat secara otomatis dari spesifikasi OpenAPI.
spesifikasi kecil
Pada artikel ini, saya akan menggunakan contoh berdasarkan format definisi REST API saya sendiri, tinyspec . Definisi terdiri dari file kecil dengan sintaks intuitif. Mereka menggambarkan titik akhir dan model data yang digunakan dalam sebuah proyek. File disimpan di sebelah kode, memberikan referensi cepat dan kemampuan untuk diedit selama penulisan kode. Tinyspec secara otomatis dikompilasi ke dalam format OpenAPI lengkap yang dapat segera digunakan dalam proyek Anda.
Saya juga akan menggunakan contoh Node.js (Koa, Express) dan Ruby on Rails, tetapi praktik yang akan saya tunjukkan berlaku untuk sebagian besar teknologi, termasuk Python, PHP, dan Java.
Dimana Spesifikasi API Rocks
Sekarang setelah kita memiliki beberapa latar belakang, kita dapat menjelajahi cara mendapatkan hasil maksimal dari API yang ditentukan dengan benar.
1. Tes Unit Titik Akhir
Behavior-driven development (BDD) sangat ideal untuk mengembangkan REST API. Yang terbaik adalah menulis pengujian unit bukan untuk kelas, model, atau pengontrol yang terpisah, tetapi untuk titik akhir tertentu. Dalam setiap pengujian, Anda meniru permintaan HTTP nyata dan memverifikasi respons server. Untuk Node.js ada paket supertest dan chai-http untuk meniru permintaan, dan untuk Ruby on Rails ada airborne.
Katakanlah kita memiliki skema User
dan titik akhir GET /users
yang mengembalikan semua pengguna. Berikut adalah beberapa sintaks tinyspec yang menjelaskan hal ini:
# user.models.tinyspec User {name, isAdmin: b, age?: i} # users.endpoints.tinyspec GET /users => {users: User[]}
Dan inilah cara kami menulis tes yang sesuai:
Node.js
describe('/users', () => { it('List all users', async () => { const { status, body: { users } } = request.get('/users'); expect(status).to.equal(200); expect(users[0].name).to.be('string'); expect(users[0].isAdmin).to.be('boolean'); expect(users[0].age).to.be.oneOf(['boolean', null]); }); });
Ruby on Rails
describe 'GET /users' do it 'List all users' do get '/users' expect_status(200) expect_json_types('users.*', { name: :string, isAdmin: :boolean, age: :integer_or_null, }) end end
Ketika kita sudah memiliki spesifikasi yang menggambarkan respon server, kita dapat menyederhanakan pengujian dan hanya memeriksa apakah respon mengikuti spesifikasi. Kita dapat menggunakan model tinyspec, yang masing-masing dapat diubah menjadi spesifikasi OpenAPI yang mengikuti format Skema JSON.
Objek literal apa pun di JS (atau Hash
di Ruby, dict
di Python, array asosiatif di PHP, dan bahkan Map
di Java) dapat divalidasi untuk kepatuhan Skema JSON. Bahkan ada plugin yang sesuai untuk menguji kerangka kerja, misalnya jest-ajv (npm), chai-ajv-json-schema (npm), dan json_matchers untuk RSpec (rubygem).
Sebelum menggunakan skema, mari impor skema tersebut ke dalam proyek. Pertama, buat file openapi.json
berdasarkan spesifikasi tinyspec (Anda dapat melakukannya secara otomatis sebelum setiap pengujian dijalankan):
tinyspec -j -o openapi.json
Node.js
Sekarang Anda dapat menggunakan JSON yang dihasilkan dalam proyek dan mendapatkan kunci definitions
darinya. Kunci ini berisi semua skema JSON. Skema mungkin berisi referensi silang ( $ref
), jadi jika Anda memiliki skema yang disematkan (misalnya, Blog {posts: Post[]}
), Anda perlu membukanya untuk digunakan dalam validasi. Untuk ini, kita akan menggunakan json-schema-deref-sync (npm).
import deref from 'json-schema-deref-sync'; const spec = require('./openapi.json'); const schemas = deref(spec).definitions; describe('/users', () => { it('List all users', async () => { const { status, body: { users } } = request.get('/users'); expect(status).to.equal(200); // Chai expect(users[0]).to.be.validWithSchema(schemas.User); // Jest expect(users[0]).toMatchSchema(schemas.User); }); });
Ruby on Rails
Modul json_matchers
mengetahui cara menangani referensi $ref
, tetapi memerlukan file skema terpisah di lokasi yang ditentukan, jadi Anda harus membagi file swagger.json
menjadi beberapa file yang lebih kecil terlebih dahulu:
# ./spec/support/json_schemas.rb require 'json' require 'json_matchers/rspec' JsonMatchers.schema_root = 'spec/schemas' # Fix for json_matchers single-file restriction file = File.read 'spec/schemas/openapi.json' swagger = JSON.parse(file, symbolize_names: true) swagger[:definitions].keys.each do |key| File.open("spec/schemas/#{key}.json", 'w') do |f| f.write(JSON.pretty_generate({ '$ref': "swagger.json#/definitions/#{key}" })) end end
Berikut adalah bagaimana tes akan terlihat seperti:
describe 'GET /users' do it 'List all users' do get '/users' expect_status(200) expect(result[:users][0]).to match_json_schema('User') end end
Menulis tes dengan cara ini sangat nyaman. Terutama jika IDE Anda mendukung pengujian dan debugging yang sedang berjalan (misalnya, WebStorm, RubyMine, dan Visual Studio). Dengan cara ini Anda dapat menghindari penggunaan perangkat lunak lain, dan seluruh siklus pengembangan API dibatasi pada tiga langkah:
- Merancang spesifikasi dalam file tinyspec.
- Menulis set lengkap tes untuk titik akhir yang ditambahkan/diedit.
- Menerapkan kode yang memenuhi pengujian.
2. Memvalidasi Data Masukan
OpenAPI tidak hanya menjelaskan format respons, tetapi juga data input. Ini memungkinkan Anda untuk memvalidasi data yang dikirim pengguna saat runtime dan memastikan pembaruan basis data yang konsisten dan aman.
Katakanlah kita memiliki spesifikasi berikut, yang menjelaskan penambalan catatan pengguna dan semua bidang yang tersedia yang diizinkan untuk diperbarui:
# user.models.tinyspec UserUpdate !{name?, age?: i} # users.endpoints.tinyspec PATCH /users/:id {user: UserUpdate} => {success: b}
Sebelumnya, kami menjelajahi plugin untuk validasi dalam pengujian, tetapi untuk kasus yang lebih umum, ada modul validasi ajv (npm) dan json-schema (rubygem). Mari kita gunakan untuk menulis pengontrol dengan validasi:
Node.js (Koa)
Ini adalah contoh untuk Koa, penerus Express—tetapi kode Express yang setara akan terlihat serupa.
import Router from 'koa-router'; import Ajv from 'ajv'; import { schemas } from './schemas'; const router = new Router(); // Standard resource update action in Koa. router.patch('/:id', async (ctx) => { const updateData = ctx.body.user; // Validation using JSON schema from API specification. await validate(schemas.UserUpdate, updateData); const user = await User.findById(ctx.params.id); await user.update(updateData); ctx.body = { success: true }; }); async function validate(schema, data) { const ajv = new Ajv(); if (!ajv.validate(schema, data)) { const err = new Error(); err.errors = ajv.errors; throw err; } }
Dalam contoh ini, server mengembalikan respons 500 Internal Server Error
jika input tidak sesuai dengan spesifikasi. Untuk menghindari hal ini, kita dapat menangkap kesalahan validator dan membentuk jawaban kita sendiri yang akan berisi informasi lebih rinci tentang bidang tertentu yang gagal validasi, dan mengikuti spesifikasinya.
Mari tambahkan definisi untuk FieldsValidationError
:
# error.models.tinyspec Error {error: b, message} InvalidField {name, message} FieldsValidationError < Error {fields: InvalidField[]}
Dan sekarang mari kita daftar sebagai salah satu kemungkinan tanggapan titik akhir:
# users.endpoints.tinyspec PATCH /users/:id {user: UserUpdate} => 200 {success: b} => 422 FieldsValidationError
Pendekatan ini memungkinkan Anda untuk menulis pengujian unit yang menguji kebenaran skenario kesalahan ketika data yang tidak valid berasal dari klien.
3. Serialisasi Model
Hampir semua kerangka kerja server modern menggunakan pemetaan relasional objek (ORM) dalam satu atau lain cara. Ini berarti bahwa sebagian besar sumber daya yang digunakan API diwakili oleh model dan instance serta koleksinya.

Proses pembentukan representasi JSON untuk entitas ini untuk dikirim dalam respons disebut serialisasi .
Ada beberapa plugin untuk melakukan serialisasi: Misalnya, sequelize-to-json (npm), act_as_api (rubygem), dan jsonapi-rails (rubygem). Pada dasarnya, plugin ini memungkinkan Anda untuk memberikan daftar bidang untuk model tertentu yang harus disertakan dalam objek JSON, serta aturan tambahan. Misalnya, Anda dapat mengganti nama bidang dan menghitung nilainya secara dinamis.
Semakin sulit saat Anda membutuhkan beberapa representasi JSON yang berbeda untuk satu model, atau saat objek berisi entitas bersarang—asosiasi. Kemudian Anda mulai membutuhkan fitur seperti pewarisan, penggunaan kembali, dan penautan serializer.
Modul yang berbeda memberikan solusi yang berbeda, tetapi mari kita pertimbangkan ini: Dapatkah spesifikasi membantu lagi? Pada dasarnya semua informasi tentang persyaratan untuk representasi JSON, semua kemungkinan kombinasi bidang, termasuk entitas yang disematkan, sudah ada di dalamnya. Dan ini berarti bahwa kita dapat menulis satu serializer otomatis.
Izinkan saya menyajikan modul sekuel kecil bersambung (npm), yang mendukung melakukan ini untuk model Sekuelisasi. Ini menerima contoh model atau array, dan skema yang diperlukan, dan kemudian mengulanginya untuk membangun objek serial. Itu juga memperhitungkan semua bidang yang diperlukan dan menggunakan skema bersarang untuk entitas terkaitnya.
Jadi, katakanlah kita perlu mengembalikan semua pengguna dengan posting di blog, termasuk komentar ke posting ini, dari API. Mari kita uraikan dengan spesifikasi berikut:
# models.tinyspec Comment {authorId: i, message} Post {topic, message, comments?: Comment[]} User {name, isAdmin: b, age?: i} UserWithPosts < User {posts: Post[]} # blogUsers.endpoints.tinyspec GET /blog/users => {users: UserWithPosts[]}
Sekarang kita dapat membangun permintaan dengan Sequelize dan mengembalikan objek serial yang sesuai dengan spesifikasi yang dijelaskan di atas persis:
import Router from 'koa-router'; import serialize from 'sequelize-serialize'; import { schemas } from './schemas'; const router = new Router(); router.get('/blog/users', async (ctx) => { const users = await User.findAll({ include: [{ association: User.posts, required: true, include: [Post.comments] }] }); ctx.body = serialize(users, schemas.UserWithPosts); });
Ini hampir ajaib, bukan?
4. Mengetik Statis
Jika Anda cukup keren untuk menggunakan TypeScript atau Flow, Anda mungkin sudah bertanya, “Apa jenis statis berharga saya?!” Dengan modul sw2dts atau swagger-to-flowtype Anda dapat menghasilkan semua tipe statis yang diperlukan berdasarkan skema JSON dan menggunakannya dalam pengujian, pengontrol, dan serializer.
tinyspec -j sw2dts ./swagger.json -o Api.d.ts --namespace Api
Sekarang kita dapat menggunakan tipe di pengontrol:
router.patch('/users/:id', async (ctx) => { // Specify type for request data object const userData: Api.UserUpdate = ctx.request.body.user; // Run spec validation await validate(schemas.UserUpdate, userData); // Query the database const user = await User.findById(ctx.params.id); await user.update(userData); // Return serialized result const serialized: Api.User = serialize(user, schemas.User); ctx.body = { user: serialized }; });
Dan tes:
it('Update user', async () => { // Static check for test input data. const updateData: Api.UserUpdate = { name: MODIFIED }; const res = await request.patch('/users/1', { user: updateData }); // Type helper for request response: const user: Api.User = res.body.user; expect(user).to.be.validWithSchema(schemas.User); expect(user).to.containSubset(updateData); });
Perhatikan bahwa definisi tipe yang dihasilkan dapat digunakan tidak hanya dalam proyek API, tetapi juga dalam proyek aplikasi klien untuk menjelaskan tipe dalam fungsi yang bekerja dengan API. (Pengembang sudut akan sangat senang tentang ini.)
5. Jenis String Kueri Casting
Jika API Anda karena alasan tertentu menggunakan permintaan dengan tipe MIME application/x-www-form-urlencoded
alih-alih application/json
, badan permintaan akan terlihat seperti ini:
param1=value¶m2=777¶m3=false
Hal yang sama berlaku untuk parameter kueri (misalnya, dalam permintaan GET
). Dalam hal ini, server web akan gagal mengenali tipe secara otomatis: Semua data akan dalam format string, jadi setelah diurai, Anda akan mendapatkan objek ini:
{ param1: 'value', param2: '777', param3: 'false' }
Dalam hal ini, permintaan akan gagal validasi skema, jadi Anda perlu memverifikasi format parameter yang benar secara manual dan mentransmisikannya ke jenis yang benar.
Seperti yang dapat Anda tebak, Anda dapat melakukannya dengan skema lama kami yang bagus dari spesifikasi. Katakanlah kita memiliki titik akhir ini dan skema berikut:
# posts.endpoints.tinyspec GET /posts?PostsQuery # post.models.tinyspec PostsQuery { search, limit: i, offset: i, filter: { isRead: b } }
Berikut adalah tampilan permintaan ke titik akhir ini:
GET /posts?search=needle&offset=10&limit=1&filter[isRead]=true
Mari kita tulis fungsi castQuery
untuk mentransmisikan semua parameter ke tipe yang diperlukan:
function castQuery(query, schema) { _.mapValues(query, (value, key) => { const { type } = schema.properties[key] || {}; if (!value || !type) { return value; } switch (type) { case 'integer': return parseInt(value, 10); case 'number': return parseFloat(value); case 'boolean': return value !== 'false'; default: return value; } }); }
Implementasi yang lebih lengkap dengan dukungan untuk skema bersarang, array, dan tipe null
tersedia di modul cast-with-schema (npm). Sekarang mari kita gunakan dalam kode kita:
router.get('/posts', async (ctx) => { // Cast parameters to expected types const query = castQuery(ctx.query, schemas.PostsQuery); // Run spec validation await validate(schemas.PostsQuery, query); // Query the database const posts = await Post.search(query); // Return serialized result ctx.body = { posts: serialize(posts, schemas.Post) }; });
Perhatikan bahwa tiga dari empat baris kode menggunakan skema spesifikasi.
Praktik terbaik
Ada sejumlah praktik terbaik yang bisa kita ikuti di sini.
Gunakan Skema Buat dan Edit Terpisah
Biasanya skema yang menggambarkan respons server berbeda dari skema yang menggambarkan input dan digunakan untuk membuat dan mengedit model. Misalnya, daftar bidang yang tersedia dalam permintaan POST
dan PATCH
harus dibatasi secara ketat, dan PATCH
biasanya memiliki semua bidang yang ditandai opsional. Skema yang menggambarkan respons bisa lebih bebas.
Saat Anda membuat titik akhir CRUDL secara otomatis, tinyspec menggunakan postfix New
dan Update
. Skema User*
dapat didefinisikan dengan cara berikut:
User {id, email, name, isAdmin: b} UserNew !{email, name} UserUpdate !{email?, name?}
Cobalah untuk tidak menggunakan skema yang sama untuk jenis tindakan yang berbeda untuk menghindari masalah keamanan yang tidak disengaja karena penggunaan kembali atau pewarisan skema yang lebih lama.
Ikuti Konvensi Penamaan Skema
Isi dari model yang sama dapat bervariasi untuk titik akhir yang berbeda. Gunakan postfix With*
dan For*
dalam nama skema untuk menunjukkan perbedaan dan tujuan. Dalam tinyspec, model juga dapat mewarisi satu sama lain. Sebagai contoh:
User {name, surname} UserWithPhotos < User {photos: Photo[]} UserForAdmin < User {id, email, lastLoginAt: d}
Postfix dapat divariasikan dan digabungkan. Nama mereka harus tetap mencerminkan esensi dan membuat dokumentasi lebih mudah dibaca.
Memisahkan Endpoint Berdasarkan Jenis Klien
Seringkali titik akhir yang sama mengembalikan data yang berbeda berdasarkan jenis klien, atau peran pengguna yang mengirim permintaan. Misalnya, titik akhir GET /users
dan GET /messages
dapat berbeda secara signifikan untuk pengguna aplikasi seluler dan manajer back office. Perubahan nama titik akhir dapat menjadi overhead.
Untuk mendeskripsikan titik akhir yang sama beberapa kali, Anda dapat menambahkan jenisnya dalam tanda kurung setelah jalur. Ini juga membuat penggunaan tag menjadi mudah: Anda membagi dokumentasi titik akhir menjadi beberapa grup, yang masing-masing ditujukan untuk grup klien API tertentu. Sebagai contoh:
Mobile app: GET /users (mobile) => UserForMobile[] CRM admin panel: GET /users (admin) => UserForAdmin[]
Alat Dokumentasi REST API
Setelah Anda mendapatkan spesifikasi dalam format tinyspec atau OpenAPI, Anda dapat membuat dokumentasi yang tampak bagus dalam format HTML dan mempublikasikannya. Ini akan membuat pengembang yang menggunakan API Anda senang, dan itu pasti mengalahkan mengisi template dokumentasi REST API dengan tangan.
Terlepas dari layanan cloud yang disebutkan sebelumnya, ada alat CLI yang mengonversi OpenAPI 2.0 ke HTML dan PDF, yang dapat digunakan ke hosting statis apa pun. Berikut beberapa contohnya:
- bootprint-openapi (npm, digunakan secara default di tinyspec)
- swagger2markup-cli (jar, ada contoh penggunaan, akan digunakan di Cloud tinyspec)
- redoc-cli (npm)
- widdershins (npm)
Apakah Anda memiliki lebih banyak contoh? Bagikan di komentar.
Sayangnya, meskipun dirilis setahun yang lalu, OpenAPI 3.0 masih kurang didukung dan saya gagal menemukan contoh dokumentasi yang tepat berdasarkan itu baik di solusi cloud maupun di alat CLI. Untuk alasan yang sama, tinyspec belum mendukung OpenAPI 3.0.
Menerbitkan di GitHub
Salah satu cara paling sederhana untuk mempublikasikan dokumentasi adalah GitHub Pages. Cukup aktifkan dukungan untuk halaman statis untuk folder /docs
Anda di pengaturan repositori dan simpan dokumentasi HTML di folder ini.
Anda dapat menambahkan perintah untuk menghasilkan dokumentasi melalui tinyspec atau alat CLI yang berbeda di file scripts/package.json
Anda untuk memperbarui dokumentasi secara otomatis setelah setiap komit:
"scripts": { "docs": "tinyspec -h -o docs/", "precommit": "npm run docs" }
Integrasi Berkelanjutan
Anda dapat menambahkan pembuatan dokumentasi ke siklus CI Anda dan mempublikasikannya, misalnya, ke Amazon S3 di bawah alamat yang berbeda tergantung pada lingkungan atau versi API (seperti /docs/2.0
, /docs/stable
, dan /docs/staging
.)
Awan Tinyspec
Jika Anda menyukai sintaks tinyspec, Anda bisa menjadi pengguna awal tinyspec.cloud. Kami berencana untuk membangun layanan cloud berdasarkan itu dan CLI untuk penyebaran otomatis dokumentasi dengan berbagai pilihan template dan kemampuan untuk mengembangkan template yang dipersonalisasi.
Spesifikasi REST: Mitos yang Luar Biasa
Pengembangan REST API mungkin merupakan salah satu proses yang paling menyenangkan dalam pengembangan web dan layanan seluler modern. Tidak ada browser, sistem operasi, dan kebun binatang ukuran layar, dan semuanya sepenuhnya di bawah kendali Anda, di ujung jari Anda.
Proses ini semakin dipermudah dengan dukungan otomatisasi dan spesifikasi terkini. API yang menggunakan pendekatan yang saya jelaskan menjadi terstruktur dengan baik, transparan, dan andal.
Intinya adalah, jika kita membuat mitos, mengapa tidak menjadikannya mitos yang luar biasa?