Skip to content

OSW — Object Storage Wrapper

By Romi Fadli, S.Kom. 12 min read v1.1.0 Storage & Data
On this page
A browser window connected directly to a storage bucket by a thick green band, while an orange box with a key above them hands the browser a dashed link.

OSW — Object Storage Wrapper — adalah service HTTP kecil, ditulis dalam Rust, yang berdiri di antara aplikasi dan object storage yang kompatibel dengan S3. Aplikasi tak pernah memegang kredensial storage dan tak pernah mengalirkan byte file lewat server-nya sendiri: aplikasi meminta tautan bertanda tangan berumur pendek kepada OSW, browser meng-upload atau men-download langsung ke storage, dan OSW mengelola nasib file sesudahnya — meng-commit-nya, menghapusnya secara lunak, memulihkannya, atau menyalinnya antar-bucket.

Saya membangunnya sekali lalu memakainya ulang di berbagai project, termasuk backend situs ini, karena semuanya butuh beberapa kemampuan yang sama dari file storage, dan membuat kemampuan-kemampuan itu benar ternyata lebih sulit daripada kelihatannya. Berbicara langsung dengan storage menyebarkan kredensial ke setiap aplikasi, membuat setiap file menempuh jalan memutar lewat server aplikasi, dan membiarkan tiap aplikasi menciptakan ulang siklus hidup yang dibutuhkan file.

Sekilas

Bahasa & frameworkRust, dengan actix-web dan client S3 murni Rust di atas rustls
ArsitekturClean Architecture
Versi1.1.0
StatusStabil, sedang dipakai
BentukService HTTP mandiri di belakang API gateway, dipakai ulang lintas project
Kode sumberPrivat
Apa module ini dan di mana posisinya sekarang.

Yang dikerjakannya

A. Membagikan tautan bertanda tangan, bukan membawa file

Presigned URL adalah tautan sementara yang ditandatangani dengan kredensial storage, yang mengizinkan satu upload atau download tertentu tanpa menyerahkan kredensialnya. OSW menerbitkannya untuk upload, download, atau keduanya, dan menamai setiap objek baru sendiri, jadi browser berbicara langsung dengan storage dan OSW tak pernah membawa satu byte pun dari file.

B. Memberi tiap file siklus hidup

File yang di-upload selagi user mengisi formulir belum menjadi bagian dari apa pun, dan file yang dihapus biasanya perlu bisa dipulihkan untuk sementara waktu. OSW memodelkan keduanya sebagai tempat pada path, di dalam bucket yang sama: upload baru mendarat di area sementara, commit memindahkannya ke path permanennya, soft delete memindahkannya ke area terhapus, dan restore memindahkannya kembali. Upload yang ditinggalkan disapu oleh storage server sendiri, lewat lifecycle rule yang mengkedaluwarsakan area sementara.

C. Menyalin antar-bucket di sisi server

Aplikasi bisa memindahkan file dari bucket privat ke bucket publik — misalnya ketika lampiran satu artikel ikut menjadi publik bersama artikelnya — atau menyalinnya di dalam satu bucket, sekaligus membuang sumbernya bila diminta. Penyalinan terjadi di dalam storage server; tak ada byte yang melewati OSW maupun aplikasi.

D. Mengembalikan banyak tautan sekaligus

Halaman yang mendaftar banyak file bisa meminta seluruh tautan download-nya dalam satu request, bila perlu dengan memeriksa dulu bahwa tiap file memang ada, lalu menerima satu baris per path yang diminta, dalam urutan yang sama.

E. Menjaga satu kontrak bagi setiap pemanggil

Setiap response, berhasil maupun gagal, datang dalam amplop JSON yang sama beserta request id, setiap kesalahan input dilaporkan sekaligus, dan pesan yang sudah dibaca aplikasi tetap sama kata per kata seperti dulu.

Antarmuka

KemampuanKegunaan
Presigntautan upload dan/atau download bertanda tangan untuk objek baru, dengan nama yang dibangkitkan
Commitmemindahkan objek yang sudah di-upload ke path permanennya
Existsapakah satu objek ada, beserta jenis, ukuran, tanggal, dan tag-nya
Downloadtautan download bertanda tangan untuk satu objek
Download batchtautan download bertanda tangan untuk banyak objek dalam satu request
Deletemenghapus objek, secara lunak bila tak ditentukan lain
Restoremengembalikan objek yang terhapus lunak ke path asalnya
Copymenyalin objek ke bucket atau key lain, bisa sekaligus membuang sumbernya
Livenessuntuk platform container
Antarmuka HTTP OSW, didokumentasikan sebagai spesifikasi OpenAPI.

OSW bisa dipasang di bawah route mana pun milik gateway. Request batch-lah yang membawa body JSON:

json
{
  "bucket": "archive",
  "paths": ["reports/2026/q3.pdf", "reports/2026/q4.pdf"],
  "verify_exists": true
}

Standar

StandarYang dijaminnya di sini
AWS Signature Version 4, autentikasi lewat querytautan bertanda tangan yang diterima storage dan kedaluwarsa tepat waktu
S3 API: HeadObject, CopyObject dengan x-amz-copy-source, DeleteObjectpemeriksaan keberadaan, copy di sisi server, dan penghapusan di server apa pun yang kompatibel S3
Addressing path-style dan virtual-hostberjalan dengan MinIO di belakang proxy maupun dengan S3 ter-hosting
UUIDv7nama objek dan request id yang tak pernah bertabrakan dan terurut menurut waktu
RFC 3339waktu kedaluwarsa tautan yang bisa dibaca client mana pun
W3C Trace Context dan OpenTelemetry (OTLP, semantic conventions)trace, log, dan metrics yang bersambung lintas service
OpenAPI 3.0.3kontrak yang terbaca mesin untuk setiap endpoint
TLS lewat rustlskoneksi terenkripsi tanpa OpenSSL di jalur kodenya
Standar yang diikuti OSW, dan yang dijamin masing-masing.

Di dalamnya

OSW mengikuti Clean Architecture: aturan storage ada di domain, use case dikelompokkan per resource, dan S3, HTTP, konfigurasi, serta telemetry masing-masing berada di balik port atau terkurung di lapisannya sendiri. Saya membangunnya ulang ke bentuk itu lapis demi lapis dari versi sebelumnya, dalam sembilan fase, dan fase terakhir membuktikan spesifikasi API yang dipublikasikan identik byte demi byte dengan yang lama, jadi tak ada aplikasi yang perlu berubah. Jam dan pembangkit id juga berupa port, sehingga setiap timestamp dan nama di dalam test bisa diramalkan, dan ke-98 test-nya berjalan tanpa storage server karena tiap use case menerima storage in-memory yang mencatat setiap panggilan.

Keputusan rancangan

Hanya OSW yang memegang key, dan tautan ditandatangani untuk alamat yang akan dipakai browser. Tak ada aplikasi yang pernah melihat kredensial storage. Signature mencakup nama host, dan host yang dipakai OSW di dalam jaringan biasanya bukan host yang bisa dijangkau browser, jadi tautan ditandatangani terhadap public origin yang akan dipakai browser. Public origin diperiksa saat service menyala, jadi nilai yang keliru gagal saat boot, bukan pada upload pertama. Handle untuk public origin dibangun sekali per bucket saat start, karena membangunnya memuat trust store sistem — sekitar 305 mikrodetik kerja disk yang memblokir — sementara tanda tangannya sendiri hanya sekitar 14, dan batch berisi 100 akan membayar ongkos pertama itu 100 kali. Content type upload ditaruh di dalam query yang ditandatangani, bukan di header bertanda tangan, jadi browser tak perlu mengulangnya persis tapi juga tak bisa mengubahnya, dan addressing path-style ikut ke handle publik supaya tautan tetap benar di belakang proxy. Objek baru dinamai {direktori}/{uuidv7}.{ekstensi} oleh OSW sendiri, jadi nama tak pernah bertabrakan, terurut menurut waktu, dan tak bisa dipilih atau ditimpa client; ekstensinya diambil dari tabel content type dengan cadangan yang masuk akal. Flag permission hanya menerbitkan tautan yang dibutuhkan pemanggil — upload, download, atau keduanya — dan umur tautan bisa diatur, dengan waktu kedaluwarsa ikut dikembalikan supaya client tahu kapan harus meminta lagi.

Status file terlihat di path-nya, dan setiap perubahan memeriksa dulu. Upload bersifat sementara kecuali pemanggil menyatakan lain. Commit dan restore adalah copy di dalam storage server yang disusul penghapusan, jadi tak ada byte yang berpindah sebesar apa pun file-nya. Path divalidasi ketat dan objeknya dicari sebelum perubahan apa pun, jadi file yang hilang menjadi 404 yang bersih, bukan error storage. Penghapusan bersifat lunak secara bawaan dan bisa dibatalkan. OSW tak menyimpan state dan tak menjalankan penyapu: upload yang ditinggalkan adalah urusan storage server, lewat lifecycle rule-nya.

Nilai bawaan aman bila tak merugikan, dan sengaja ditiadakan bila akan menyembunyikan kesalahan. Bucket yang boleh disentuh OSW persis bucket yang dikonfigurasikan untuknya, dan ia menolak menyala bila tak ada satu pun. Bucket tak dikenal adalah kesalahan pemanggil, 400, bukan kesalahan server. Request satu-file jatuh ke bucket bawaan, tetapi copy wajib menyebut kedua bucket, karena jatuh diam-diam akan menyembunyikan kesalahan pemanggil.

Copy antar-bucket adalah copy di sisi server yang sungguhan, dan tak pernah menghilangkan sumbernya. Client S3 yang dipakai OSW hanya bisa menyalin di dalam satu bucket, jadi copy antar-bucket menandatangani PUT ke bucket tujuan dengan header x-amz-copy-source yang ikut ditandatangani, lalu mengirimnya lewat satu client HTTP yang dipakai ulang. Tautan internal itu tak pernah keluar dari proses. Header-nya dikirim sekaligus ditandatangani, kalau tidak signature-nya tak akan cocok; key sumber di-encode per segmen, dengan spasi sebagai %20 dan tak pernah +, yang akan dibaca storage sebagai tanda plus. Copy yang menjawab 200 tapi membawa error di body-nya dihitung gagal, karena S3 bisa melaporkan kegagalan copy yang terlambat dengan cara itu. Sumber tak pernah dihapus bila copy gagal; bila copy berhasil tapi penghapusan sumbernya tidak, jawabannya tetap 200 dengan source_deleted: false — tim backend yang meminta endpoint ini menginginkannya, supaya client tak mengulang copy yang sudah terjadi. Key tujuan secara bawaan sama dengan key sumber, copy di dalam satu bucket diizinkan, dan setiap pesan validasi menyebut sisi mana yang dimaksud. TLS-nya hanya rustls, dengan tipe HTTP dipatok ke versi yang diharapkan client S3.

Batch gagal hanya di tempat yang memang harus gagal. Endpoint batch ditambahkan di samping endpoint satu-file sesudah konsumen menyiasatinya dengan menembakkan satu request per file sekaligus, yang hanya memindahkan bebannya ke OSW. Persoalan yang mengenai setiap baris — bucket tak dikenal, path terlalu banyak, daftar kosong — menggagalkan seluruh request; persoalan pada satu baris hanya menggagalkan baris itu, dan setiap tautan lain tetap dikembalikan, dalam urutan saat diminta, dengan path ganda dipantulkan sekali per kemunculannya:

json
{
  "full_path_file": "reports/2026/q4.pdf",
  "download_url": null,
  "expired_at": null,
  "error": "not_found"
}

Setiap baris membawa empat key yang sama dengan null eksplisit, berisi tautan atau error dan tak pernah keduanya, dan memakai kosakata kecil milik domain: not_found, invalid_path, dan unavailable, yang terakhir sengaja dipisahkan supaya gangguan storage yang sesaat dicoba ulang, bukan dianggap file yang hilang. Perlu tidaknya tiap file diperiksa dulu tak punya nilai bawaan, karena memeriksa menelan satu request storage per file dan melewatinya menghilangkan sinyal "tidak ada". Pemeriksaan berjalan dengan konkurensi yang dibatasi, batas yang dipisahkan dari batas jumlah path karena keduanya ada dengan alasan berbeda, jadi satu pemanggil tak bisa menyingkirkan pemanggil lain. Satu waktu kedaluwarsa dihitung untuk seluruh batch, error tiap baris dicatat di tempat terjadinya, dan tiap baris memakai ulang aturan path satu-file.

Satu kontrak, dijaga persis. Setiap response dibungkus satu amplop, yang menggantikan selusin helper yang nyaris kembar. Batas body JSON diatur dengan sengaja, dan JSON yang rusak tetap menerima amplopnya, bukan error teks polos bawaan framework. Validasi mengumpulkan setiap persoalan sebelum menjawab. Error use case berupa tipe yang dipetakan lapisan HTTP ke status code, menggantikan trik lama yang membaca awalan string error, dan 500 hanya menyebut operasi mana yang gagal, dengan penyebabnya di log. Body 401 adalah satu-satunya pengecualian yang disengaja, dibiarkan dalam bentuk lamanya demi client yang sudah membacanya, begitu pula pesan-pesan yang di-parse client itu. Objek hilang yang dilaporkan client S3 dengan caranya sendiri yang tak konsisten tetap dikenali sebagai 404.

Identitas datang dari gateway, dan setiap request bisa ditelusuri. OSW berdiri di belakang API gateway yang mengautentikasi setiap pemanggil dan hanya meneruskan identitas yang sudah terverifikasi. Request id diterima bila pemanggil mengirimnya dan dibangkitkan bila tidak, lalu dipantulkan kembali. Telemetry beralih dari menulis langsung ke Elasticsearch menjadi OpenTelemetry lewat collector: trace yang masuk diteruskan, metrics memakai delta temporality karena exporter Elasticsearch milik collector membuang histogram dan counter bila tidak, metrics HTTP diberi label pola route, bukan path mentah, alamat client diambil dari header yang diteruskan hanya bila datang dari trusted proxy, key sensitif disamarkan di log, dan telemetry bisa dimatikan sepenuhnya, dengan flush saat shutdown ketika menyala. Konfigurasi dibaca sekali, saat start.

Ia dikirim kecil dan berjalan tanpa hak istimewa. Image-nya di-build dalam dua tahap, berjalan sebagai user non-root, dan membaca konfigurasinya dari file yang di-mount.

Spesifikasi

MasukanRequest dari aplikasi di belakang API gateway, membawa identitas yang terverifikasi
KeluaranTautan bertanda tangan beserta waktu kedaluwarsanya, metadata objek, hasil siklus hidup dan copy, semuanya dalam satu amplop JSON
StorageServer apa pun yang kompatibel S3, MinIO di deployment saya, dengan lifecycle rule yang mengkedaluwarsakan upload yang ditinggalkan
ObservabilityTrace, log, dan metrics OpenTelemetry lewat OTLP, atau standard output saja
Test98, tanpa storage server
Berjalan sebagaiImage container yang di-build dua tahap, sebagai user non-root, dengan konfigurasi di-mount dari file
Yang diterima, dikembalikan, dan dibutuhkan OSW.

Terkait

Sebelum OSW dipasang di depan satu storage server, server itu diperiksa dengan script ini.

Membuktikan storage di belakang OSW sudah siap sebelum apa pun bergantung padanya.

OSW menjadi studi kasus artikel ini, tentang kode yang bisa diikuti pembaca berikutnya.

Template yang menyalakan object storage server bersama OSW untuk project baru.

Ke mana trace, log, dan metrics OSW pergi begitu meninggalkan service-nya.

Istilah

Object storage
Storage yang menyimpan file sebagai objek utuh di dalam bucket bernama, dicapai lewat HTTP.
Presigned URL
Tautan sementara yang ditandatangani dengan kredensial storage, yang mengizinkan satu upload atau download tertentu tanpa memberikan kredensial itu kepada client.
Soft delete
Memindahkan file ke area terhapus, bukan memusnahkannya, sehingga bisa dipulihkan.
Lifecycle rule
Aturan yang dipasang di storage server untuk mengkedaluwarsakan atau memindahkan objek secara otomatis, misalnya semua yang ada di bawah satu prefix sesudah satu hari.
API gateway
Service di depan service lain yang mengautentikasi pemanggil dan hanya meneruskan identitas yang sudah terverifikasi kepada mereka.
Port
Antarmuka yang ditetapkan lapisan dalam dan diimplementasikan lapisan luar, sehingga aturan tak pernah bergantung pada tool tertentu.

Technologies covered

  • Rust
  • MinIO
  • S3

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

Employees and administrators use the Web App, which talks to the Core API; the Core API uses storage, regions, face and preview services, sends documents to the Aurograf signing platform, sends logs and traces to the log stack, and imports from the old system

SIMPEG

Programming

Sistem informasi kepegawaian Pemerintah Kota Tangerang Selatan: pegawai mengurus record-nya sendiri dan mengikuti layanannya, admin bekerja tepat pada bagian organisasinya, dan setiap aturan berasal dari peraturan yang menetapkannya. Saya pimpin dan bangun sejak 2023.

10 min read Read more →
The core API at the centre, fed by storage, address, face and preview services, with documents sent across a boundary to an external signing platform

Inti Laravel sistem kepegawaian aparatur sipil negara: record pegawai dan riwayat karier, usulan swalayan yang diperiksa, keputusan kenaikan gaji berkala, kenaikan pangkat, dan pensiun, aturan dari peraturan, bagan organisasi berversi, dan akses berlingkup sampai satu unit.

11 min read Read more →
A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

Inti Laravel dari platform penandatanganan dokumen: memegang user, identitas, organisasi, sistem client, dokumen beserta versi bertanda tangannya, stempel, dan sertifikat, serta mengorkestrasi service yang menandatangani, menstempel waktu, merender, dan menyimpan — tanpa pernah memegang file.

9 min read Read more →
Three camera frames of a face, facing forward, turned right, and turned left, each checked off, flowing into locked private storage that a reviewer looks into

Service Python yang meminta orang di depan kamera menghadap depan, menoleh ke kanan, lalu ke kiri, menangkap satu frame tajam per pose, dan menyimpan ketiganya sebagai bukti privat untuk peninjau manusia — membuktikan orang hidup hadir, bukan siapa dia.

10 min read Read more →
A signed PDF with a QR stamp, wrapped in four nested frames for the signature, its timestamp, the embedded validation data, and the archive timestamp

Service Python yang menandatangani PDF sampai PAdES B-LTA dengan stempel yang terlihat — menanamkan stempel waktu, sertifikat, dan bukti pencabutan, lalu stempel waktu arsip — supaya tanda tangan tetap bisa diverifikasi lama setelah sertifikatnya kedaluwarsa, dan memverifikasi PDF bertanda tangan.

10 min read Read more →
PDF pages of different sizes passing through a renderer that reads from and writes back to storage, producing thumbnails of one uniform size

Service Rust tanpa state yang merender halaman pertama PDF menjadi thumbnail WebP yang tajam dan berukuran seragam, membaca PDF dan menulis gambarnya lewat dua presigned link, sehingga tak ada aplikasi yang mengalirkan file atau memegang kredensial storage.

8 min read Read more →
A purple root circle linked to a blue and an orange intermediate circle, which lead to a signed document with a green seal and to a clock.

Aurograf

DevOps

Platform tanda tangan digital Pemerintah Kota Tangerang Selatan: penanda tangan membuktikan identitasnya, menandatangani PDF hingga PAdES B-LTA dengan sertifikat dari otoritas milik platform sendiri, dan siapa pun bisa memverifikasinya — versi 2, yang saya rancang, bangun, dan operasikan.

10 min read Read more →

Related Articles

A four-level tree of Indonesian regions, with one chosen path coloured level by level, feeding a form of four cascading dropdowns in the same colours

Service Rust yang menyajikan wilayah administratif Indonesia — 38 provinsi sampai 83.768 desa, berkunci kode resmi Kementerian Dalam Negeri — sebagai empat lookup bertingkat dengan pencarian, supaya setiap form alamat di setiap project mendapat jawaban yang sama.

10 min read Read more →
Four concentric circles with arrows coming in from a database, a browser window, and a message queue, all pointing toward the centre.

Clean Architecture

Programming

Clean Architecture adalah salah satu arsitektur yang saya pakai: kenapa saya memilihnya, kesepakatan folder dan penamaan yang diikuti tiap module saya di Rust, Python, Dart, Node.js, dan C, pertanyaan yang saya pakai untuk memeriksa module, dan tempat saya memakai arsitektur lain.

12 min read Read more →