Porting APIs ke TypeScript sebagai Pemecah Masalah

Frontend Bereaksi dari Program Jalankan telah dikonversi dari JavaScript ke TypeScript. Tetapi backend, ditulis dalam Ruby, tidak menyentuh. Namun, masalah yang terkait dengan backend ini membuat pengembang proyek berpikir tentang beralih dari Ruby ke TypeScript. Terjemahan dari materi yang kami terbitkan hari ini dikhususkan untuk kisah porting program Execute backend dari Ruby ke TypeScript, dan masalah apa yang membantu menyelesaikannya.



Menggunakan Ruby backend, kadang-kadang kita lupa bahwa beberapa properti API menyimpan array string, bukan string sederhana. Terkadang kami mengubah fragmen API yang diakses di tempat yang berbeda, tetapi lupa memperbarui kode di salah satu tempat ini. Ini adalah masalah umum dari bahasa dinamis yang merupakan ciri khas dari sistem mana pun yang kodenya tidak 100% dicakup oleh pengujian. (Ini, meskipun kurang umum, terjadi ketika kode sepenuhnya dicakup oleh tes.)

Pada saat yang sama, masalah ini telah menghilang dari frontend sejak kami beralih ke TypeScript. Saya memiliki lebih banyak pengalaman dalam pemrograman server daripada di klien, tetapi, meskipun demikian, saya membuat lebih banyak kesalahan ketika bekerja dengan backend, dan tidak dengan frontend. Semua ini menunjukkan bahwa backend juga harus dikonversi ke TypeScript.

Saya porting backend dari Ruby ke TypeScript pada Maret 2019 dalam waktu sekitar 2 minggu. Dan semuanya berjalan sebagaimana mestinya! Kami menerapkan kode baru dalam produksi pada 14 April 2019. Itu adalah versi beta yang tersedia untuk sejumlah pengguna. Setelah itu, tidak ada yang rusak. Pengguna bahkan tidak melihat apa pun. Berikut ini adalah grafik yang menggambarkan keadaan basis kode kami sebelum dan segera setelah transisi. Sumbu x mewakili waktu (dalam hari), sumbu y mewakili jumlah baris kode.


Menerjemahkan frontend dari JavaScript ke TypeScript, dan menerjemahkan backend dari Ruby ke TypeScript

Selama proses porting, saya menulis sejumlah besar kode tambahan. Jadi, kami memiliki alat kami sendiri untuk menjalankan tes dengan volume 200 baris. Kami memiliki pustaka 120-line untuk bekerja dengan database, serta pustaka routing yang lebih besar untuk API, yang menghubungkan kode front-end dan back-end.

Dalam infrastruktur kami sendiri, hal yang paling menarik untuk dibicarakan adalah router. Ini adalah pembungkus untuk Express, memastikan aplikasi yang benar dari jenis yang digunakan dalam kode klien dan server. Ini berarti bahwa ketika satu bagian dari API berubah, yang lain bahkan tidak dapat dikompilasi tanpa membuat perubahan untuk menghilangkan perbedaan.

Berikut ini adalah backend handler yang mengembalikan daftar posting blog. Ini adalah salah satu fragmen kode serupa yang paling sederhana dalam sistem:

router.handleGet(api.blog, async () => {
  return {
    posts: blog.posts,
  }
})

Jika kita mengubah nama kunci postsmenjadi blogPosts, kita mendapatkan kesalahan kompilasi, teks yang ditunjukkan di bawah ini (di sini, untuk singkatnya, informasi tentang jenis objek dihilangkan.)

Property 'posts' is missing in type '...' but required in type '...'.

Setiap titik akhir ditentukan oleh objek tampilan api.someNameHere. Objek ini dibagikan oleh klien dan server. Perhatikan bahwa jenis tidak disebutkan secara langsung dalam deklarasi handler. Mereka semua disimpulkan dari argumen api.blog.

Pendekatan ini berfungsi untuk titik akhir sederhana, seperti titik akhir yang dijelaskan di atas blog. Tetapi cocok untuk titik akhir yang lebih kompleks. Misalnya, API titik akhir untuk bekerja dengan pelajaran memiliki kunci bersarang yang mendalam dari tipe logis .lesson.steps[index].isInteractive. Berkat semua ini, sekarang tidak mungkin untuk melakukan kesalahan berikut:

  • Jika kami mencoba mengakses isinteractiveklien, atau mencoba mengembalikan kunci seperti itu dari server, kode tidak akan dikompilasi. Nama kuncinya harus seperti isInteractive, dengan huruf besar I.
  • isInteractive β€” .
  • isInteractive number, , , .
  • API, , isInteractive β€” , , , , , , , .

Perhatikan bahwa semua ini termasuk pembuatan kode. Ini dilakukan dengan menggunakan io-ts dan beberapa ratus baris kode dari router kita sendiri.

Mendeklarasikan jenis API membutuhkan pekerjaan tambahan, tetapi pekerjaan itu sederhana. Saat mengubah struktur API, kita perlu tahu bagaimana struktur kode berubah. Kami membuat perubahan pada deklarasi API, dan kemudian kompiler mengarahkan kami ke semua tempat di mana kode perlu diperbaiki.

Sulit untuk menghargai pentingnya mekanisme ini sampai Anda menggunakannya sebentar. Kita dapat memindahkan objek besar dari satu tempat di API ke tempat lain, mengganti nama kunci, kita dapat membagi objek besar menjadi beberapa bagian, menggabungkan objek kecil menjadi satu objek, membagi atau menggabungkan seluruh titik akhir. Dan kita bisa melakukan semua ini tanpa khawatir tentang fakta bahwa kita lupa membuat perubahan yang sesuai untuk kode klien atau server.

Ini adalah contoh nyata. Baru-baru ini saya menghabiskan sekitar 20 jam pada empat hari libur mendesain ulang Program Jalankan API . Seluruh struktur API telah berubah. Ketika membandingkan klien baru dan kode server dengan yang lama, puluhan ribu perubahan garis dicatat. Saya mendesain ulang kode routing sisi server (seperti di atashandleGet) Saya menulis ulang semua deklarasi tipe untuk API, membuat banyak dari mereka perubahan struktural besar. Dan, di samping itu, saya menulis ulang semua bagian klien di mana API yang diubah dipanggil. Selama pekerjaan ini, 246 dari 292 file sumber diubah.

Dalam sebagian besar pekerjaan ini, saya hanya mengandalkan sistem tipe. Pada jam terakhir dari kasus 20 jam ini, saya mulai menjalankan tes, yang, sebagian besar, berakhir dengan sukses. Pada akhirnya, kami melakukan tes penuh dan menemukan tiga kesalahan kecil.

Ini semua adalah kesalahan logis: kondisi yang secara tidak sengaja mengarahkan program ke tempat yang salah. Biasanya, sistem tipe tidak membantu dalam menemukan kesalahan seperti itu. Butuh beberapa menit untuk memperbaiki kesalahan ini. API yang didesain ulang ini digunakan beberapa bulan yang lalu. Ketika Anda membaca sesuatusitus kami - API inilah yang mengeluarkan materi yang relevan.

Ini tidak berarti bahwa sistem tipe statis menjamin bahwa kode akan selalu benar. Sistem ini tidak memungkinkan untuk melakukan tanpa tes. Tetapi sangat menyederhanakan refactoring.

Saya akan memberi tahu Anda tentang pembuatan kode otomatis. Yaitu, kami menggunakan skema untuk menghasilkan definisi tipe dari struktur database kami. Sistem terhubung ke database Postgres, menganalisis tipe kolom dan menulis definisi tipe TypeScript yang sesuai ke file biasa yang .d.tsdigunakan oleh aplikasi.

File dengan tipe skema basis data selalu diperbarui oleh skrip migrasi kami setiap kali diluncurkan. Karena itu, kita tidak harus secara manual mendukung jenis ini. Model menggunakan definisi tipe database untuk memastikan bahwa kode aplikasi dengan benar mengakses semua yang tersimpan dalam database. Tidak ada tabel yang hilang, tidak ada kolom yang hilang, atau entri nulldi kolom yang tidak mendukung null. Kami ingat untuk memproses dengan benar nulldalam kolom yang mendukung null. Dan semua ini diperiksa secara statis pada waktu kompilasi.

Semua ini bersama-sama menciptakan rantai transfer informasi yang diketik secara statis yang dapat diandalkan, mulai dari database hingga properti komponen Bereaksi di frontend:

  • , ( API) , .
  • API , API, ( ) .
  • React- , API, .

Saat mengerjakan materi ini, saya tidak dapat mengingat satu pun kasus ketidakkonsistenan dalam kode yang terkait dengan API yang lulus kompilasi. Kami tidak memiliki kegagalan produksi yang muncul karena kode klien dan server terkait dengan API memiliki gagasan berbeda tentang formulir data. Dan semua ini bukan hasil pengujian otomatis. Kami, untuk API itu sendiri, tidak menulis tes.

Ini menempatkan kita dalam posisi yang sangat menyenangkan: kita dapat berkonsentrasi pada bagian terpenting dari aplikasi. Saya menghabiskan sedikit waktu melakukan konversi jenis. Jauh lebih sedikit daripada yang saya habiskan mengidentifikasi penyebab kesalahan membingungkan yang menembus lapisan kode yang ditulis dalam Ruby atau JavaScript, dan kemudian menyebabkan pengecualian aneh di suatu tempat yang sangat jauh dari sumber kesalahan.

Ini adalah bagaimana proyek terlihat setelah menerjemahkan backend ke TypeScript. Seperti yang Anda lihat, banyak kode telah ditulis sejak transisi. Kami punya cukup waktu untuk mengevaluasi konsekuensi dari keputusan tersebut.


TypeScript digunakan di bagian depan dan belakang proyek.

Di sini kita belum mengajukan pertanyaan yang biasa untuk publikasi seperti itu, yaitu untuk mencapai hasil yang sama bukan melalui pengetikan, tetapi melalui penggunaan tes. Hasil seperti itu tidak dapat dicapai hanya dengan menggunakan tes. Kami, sangat mungkin, akan berbicara lebih banyak tentang ini.

Pembaca yang budiman! Apakah Anda menerjemahkan proyek yang ditulis dalam bahasa lain ke dalam TypeScript?


All Articles