Saya melalui situasi yang sangat menarik di tempat kerja dan saya ingin berkongsi penyelesaiannya di sini.
Bayangkan anda perlu memproses satu set data. Dan untuk menangani set data ini, anda mempunyai beberapa strategi berbeza untuk ini. Contohnya, saya perlu mencipta strategi untuk cara mengambil koleksi data daripada S3, atau contoh dalam repositori setempat, atau diluluskan sebagai input.
Dan sesiapa yang akan menentukan strategi ini adalah orang yang membuat permintaan:
Saya ingin mendapatkan data dalam S3. Ambil data yang dijana pada hari X antara jam H1 dan H2, iaitu daripada klien Abóbora. Dapatkan 3000 data terakhir yang memenuhi ini.
Atau sebaliknya:
Ambil data contoh yang anda ada di sana, salin 10000 kali untuk melakukan ujian tekanan.
Atau pun:
Saya mempunyai direktori ini, anda juga mempunyai akses kepadanya. Dapatkan segala-galanya dalam direktori itu dan secara rekursif ke dalam subdirektori.
Dan juga akhirnya:
Ambil unit data ini yang terdapat dalam input dan gunakannya.
Fikiran pertama saya ialah: "bagaimana saya boleh menentukan bentuk input saya dalam Java?"
Dan saya mencapai kesimpulan pertama, sangat penting untuk projek: "anda tahu apa? Saya tidak akan menentukan bentuk. Tambah Peta
Selain itu, kerana saya tidak meletakkan sebarang bentuk dalam DTO, saya mempunyai kebebasan sepenuhnya untuk mencuba input.
Jadi selepas mewujudkan bukti konsep, kita sampai pada situasi: kita perlu keluar daripada tekanan POC dan beralih kepada sesuatu yang hampir dengan penggunaan sebenar.
Perkhidmatan yang saya lakukan adalah untuk mengesahkan peraturan. Pada asasnya, apabila menukar peraturan, saya perlu mengambil peraturan itu dan memadankannya dengan peristiwa yang berlaku dalam aplikasi pengeluaran. Atau, jika aplikasi telah ditukar dan tiada pepijat, jangkaan ialah keputusan untuk peraturan yang sama akan kekal sama untuk data yang sama; Sekarang, jika keputusan untuk peraturan yang sama menggunakan set data yang sama diubah... nah, itu potensi masalah.
Jadi, saya memerlukan aplikasi ini untuk menjalankan ujian belakang peraturan. Saya perlu menekan aplikasi sebenar yang menghantar data untuk penilaian dan peraturan yang dipersoalkan. Penggunaan ini agak pelbagai:
Jadi, untuk itu, saya memerlukan beberapa strategi untuk asal usul peristiwa:
Dan saya juga memerlukan strategi yang berbeza daripada peraturan saya:
Bagaimana untuk menangani perkara ini? Baiklah, biarkan pengguna memberikan data!
Adakah anda tahu sesuatu yang selalu menarik perhatian saya tentang json-schema? Ini di sini:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Medan ini bermula dengan $. Pada pendapat saya, mereka digunakan untuk menunjukkan metadata. Jadi mengapa tidak gunakan ini dalam input data untuk menunjukkan metadata strategi mana yang sedang digunakan?
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Sebagai contoh, saya boleh memesan 15000 salinan data yang saya ada sebagai sampel. Atau minta beberapa perkara daripada S3, membuat pertanyaan dalam Athena:
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Atau dalam laluan setempat?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Jadi saya boleh mewakilkan kepada pemilihan strategi di hadapan.
Pendekatan pertama saya untuk menangani strategi ialah:
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Jadi arkitek saya bertanya dua soalan semasa semakan kod:
Apa yang saya faham daripada ini? Menggunakan fasad adalah idea yang baik untuk menyerahkan pemprosesan ke sudut yang betul dan... untuk melepaskan kawalan manual?
Nah, banyak keajaiban berlaku kerana Musim Bunga. Memandangkan kami berada di rumah Java dengan kepakaran Java, mengapa tidak menggunakan Java/Spring idiomatik, bukan? Hanya kerana Saya sebagai individu mendapati beberapa perkara sukar difahami tidak semestinya ianya rumit. Jadi, mari kita menghayati dunia sihir suntikan pergantungan Java.
Apa dulu:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Menjadi:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Jadi lapisan pengawal saya tidak perlu mengurus ini. Biarkan ia pada fasad.
Jadi, bagaimana kita akan membuat fasad? Nah, untuk memulakan, saya perlu menyuntik semua objek ke dalamnya:
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Ok, untuk DataLoader utama saya menulisnya sebagai @Primary sebagai tambahan kepada @Service. Selebihnya saya tulis sahaja dengan @Service.
Uji ini di sini, tetapkan getDataLoader untuk mengembalikan null hanya untuk mencuba cara Spring memanggil pembina dan... ia berfungsi. Sekarang saya perlu nota dengan metadata setiap perkhidmatan strategi yang mereka gunakan...
Bagaimana untuk melakukan ini...
Nah, lihat! Di Java kami mempunyai anotasi! Saya boleh mencipta anotasi masa jalan yang mempunyai di dalamnya strategi yang digunakan oleh komponen itu!
Jadi saya boleh mempunyai sesuatu seperti ini dalam AthenaQueryDataLoader:
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Dan saya juga boleh mempunyai alias, mengapa tidak?
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Dan tunjukkan!
Tetapi bagaimana untuk mencipta anotasi ini? Nah, saya memerlukannya untuk mempunyai atribut yang merupakan vektor rentetan (pengkompil Java sudah berurusan dengan menyediakan rentetan tunggal dan mengubahnya menjadi vektor dengan 1 kedudukan). Nilai lalai ialah nilai. Ia kelihatan seperti ini:
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Jika medan anotasi bukan nilai, saya perlu menyatakannya dengan jelas dan itu akan kelihatan hodoh, seperti dalam anotasi EstrategiaFeia:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Bunyinya tidak begitu natural pada pendapat saya.
Baiklah, memandangkan itu, kami masih memerlukan:
Untuk mengekstrak anotasi, saya perlu mempunyai akses kepada kelas objek:
dataLoaderFacade.loadData(inputDados, workingPath);
Selain itu, bolehkah saya bertanya sama ada kelas ini diberi anotasi dengan anotasi seperti Strategi:
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
Adakah anda ingat bahawa ia mempunyai medan nilai? Nah, medan ini mengembalikan vektor rentetan:
@Service @Primary @Estrategia("athena-query") public class AthenaQueryDataLoader implements DataLoader { // ... }
Tunjukkan! Tetapi saya mempunyai cabaran, kerana sebelum ini saya mempunyai objek jenis T dan sekarang saya mahu memetakan objek yang sama ke dalam, baik, (T, String)[]. Dalam strim, operasi klasik yang melakukan ini ialah flatMap. Dan Java juga tidak membenarkan saya memulangkan tupel seperti itu entah dari mana, tetapi saya boleh mencipta rekod dengannya.
Ia akan kelihatan seperti ini:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://json-schema.org/draft/2020-12/schema", "$vocabulary": { //... } }
Bagaimana jika terdapat objek yang tidak dianotasi dengan strategi? Adakah ia akan memberi NPE? Lebih baik tidak, mari kita menapisnya sebelum NPE:
{ "dados": { "$strategy": "sample", "copias": 15000 }, //... }
Memandangkan itu, saya masih perlu menyusun peta. Dan, lihatlah: Java sudah menyediakan pengumpul untuk ini! Collector.toMap(keyMapper, valueMapper)
{ "dados": { "$strategy": "athena-query", "limit": 15000, "inicio": "2024-11-25", "fim": "2024-11-26", "cliente": "Abóbora" }, //... }
Setakat ini, ok. Tetapi flatMap sangat mengganggu saya. Terdapat API Java baharu yang dipanggil mapMulti, yang mempunyai potensi untuk membiak:
{ "dados": { "$strategy": "localpath", "cwd": "/home/jeffque/random-project-file", "dir": "../payloads/esses-daqui/top10-hard/" }, //... }
Kecantikan. Saya mendapatkannya untuk DataLoader, tetapi saya juga perlu melakukan perkara yang sama untuk RuleLoader. Atau mungkin tidak? Jika anda perasan, tiada apa-apa dalam kod ini yang khusus untuk DataLoader. Kita boleh abstrak kod ini!!
public DataLoader getDataLoader(Map<String, Object> inputDados) { final var strategy = (String) inputDados.get("$strategy"); return switch (strategy) { case "localpath" -> new LocalpathDataLoader(); case "sample" -> new SampleDataLoader(resourcePatternResolver_spring); case "athena-query" -> new AthenaQueryDataLoader(athenaClient, s3Client); default -> new AthenaQueryDataLoader(athenaClient, s3Client); } }
Atas sebab utilitarian semata-mata, saya meletakkan algoritma ini dalam anotasi:
final var dataLoader = getDataLoader(inputDados) dataLoader.loadData(inputDados, workingPath);
Dan untuk fasad? Nah, kerja yang baik untuk mengatakan perkara yang sama. Saya memutuskan untuk mengabstrak ini:
dataLoaderFacade.loadData(inputDados, workingPath);
Dan fasad kelihatan seperti ini:
@Service // para o Spring gerenciar esse componente como um serviço public class DataLoaderFacade implements DataLoader { public DataLoaderFacade(DataLoader primaryDataLoader, List<DataLoader> dataLoaderWithStrategies) { // armazena de algum modo } @Override public CompletableFuture<Void> loadData(Map<String, Object> input, Path workingPath) { return getDataLoader(input).loadData(input, workingPath); } private DataLoader getDataLoader(Map<String, Object> input) { final var strategy = input.get("$strategy"); // magia... } }
Atas ialah kandungan terperinci Menggunakan anotasi dalam Java untuk membuat strategi. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!