Feathers: Inisialisasi layanan Feathers secara asinkron

Dibuat pada 5 Feb 2017  ·  64Komentar  ·  Sumber: feathersjs/feathers

Ini adalah masalah nyata yang baru-baru ini saya alami, tetapi saya kira ini menyangkut kerangka kerja secara keseluruhan. Saya pikir wajar jika layanan Feathers diinisialisasi secara tidak sinkron. Mereka dapat memilih konfigurasi mereka dari sumber asinkron, atau bisa juga sesuatu yang lain.

Dalam kebanyakan kasus, inisialisasi aplikasi sinkron dianggap, tetapi saya mengalami masalah desain saat mulai menggunakan Sequelize.

Sementara Mongoose melakukan pekerjaan yang baik dalam mengumpulkan janji di dalam untuk tindakan inisialisasi (koneksi, pembuatan indeks), jadi kueri secara otomatis dirantai ke janji awal, Sequelize tidak melakukan hal seperti itu, misalnya ketika sync() dipanggil.

Saya berharap bahwa database SQL siap pada saat server web telah dimulai, tetapi ketika aplikasi didefinisikan secara sinkron, itu tidak akan terjadi. Dan inilah yang terjadi dengan pengaturan Feathers yang khas. Dalam beberapa kasus, inisialisasi asinkron mungkin memerlukan beberapa md, dalam kasus lain mungkin memerlukan beberapa detik, sementara server akan mengotori log dengan kesalahan yang seharusnya tidak pernah terjadi secara normal.

Masalah lainnya adalah penanganan kesalahan. Jika ada kesalahan yang tidak tertangkap selama inisialisasi layanan asinkron, langkah yang masuk akal adalah menghentikan inisialisasi aplikasi dan menangkap kesalahan di satu tempat.

Berikut adalah contoh kecil yang menjelaskan ide dan didasarkan pada struktur yang diusulkan oleh template generator Feathers.

aplikasi.js:

const app = require('feathers')();

// THIS!
app.set('feathersServicePromises', []);

app
.use(require('body-parser').json())
.configure(require('feathers-hooks'))
.configure(require('feathers-rest'))
.configure(require('./middleware'))
.configure(require('./services'));

// THIS!
Promise.all(app.get('feathersServicePromises'))
.then(() => app.listen(3000), console.error);

layanan/index.js:

module.exports = () => {
    const app = this;

    app
    .configure(require('./foo-async-service')
    .configure(require('./bar-async-service')
    .configure(require('./baz-sync-service')
};

services/foo-async-service.js:

const sequelize = require('./common/sequelize');

module.exports = () => {
    const app = this;
    const fooModel = sequelize.model('foo');

    // AND THIS!
    const initPromise = fooModel.sync();
    app.get('feathersServicePromises').push(initPromise);

    app.use('/foo', require('feathers-sequelize')({ Model: fooModel  }));
    app.service('/foo');
};

Tempat-tempat menarik di sini adalah initPromise dan Promise.all(...) . Akan lebih mudah untuk mengembalikan initPromise dari fungsi layanan, seperti yang dilakukan di kait. Dan Promise.all(...) mungkin bisa dijalankan dengan malas dengan metode app objek toPromise() atau properti getter promise . Jadi bisa jadi

module.exports = () => {
    ...
    return fooModel.sync();
};

dalam fungsi pelayanan, dan

app
...
.configure(require('./services'))
.promise
.then(() => app.listen(3000), console.error);

di titik masuk.

Saya kira pola yang saya gunakan untuk inisialisasi async sudah sederhana dan bersih, tetapi akan lebih bagus jika kerangka kerja akan mengambil pekerjaan ini.

Saran saya adalah menempatkan fitur ini dalam pertimbangan.

Saya akan menghargai setiap pemikiran tentang masalah ini.

Breaking Change Core Feature

Komentar yang paling membantu

Ini telah menjadi diskusi yang cukup panjang tetapi inilah proposal untuk menyelesaikan masalah pengaturan aplikasi asinkron. Ternyata, Feathers sudah memiliki cara yang cukup mapan untuk menangani alur kerja metode asinkron, yaitu Hooks . Berdasarkan #924 oleh @bertho-zero untuk memungkinkan penambahan fungsionalitas hook ke metode asinkron apa pun, kami sekarang memiliki cara untuk membuat pengaturan aplikasi atau layanan tertentu menjadi lebih mudah.

Asinkron service.setup , app.setup dan app.listen

Ini akan menjadi perubahan utama yang menjadikannya inti Feathers ( @feathersjs/feathers ) versi 4. service.setup , app.setup dan app.listen akan mengembalikan Janji dan berjalan secara tidak sinkron.

__Sebelum:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');
const server = app.listen(port);

server.on('listening', () =>
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
);

__Sekarang:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');

app.listen(port).then(server => {
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
});

Kait pengaturan layanan

Juga dilacak di https://github.com/feathersjs/feathers/issues/853 , service.setup sekarang akan menjadi asinkron (kembalikan janji) yang memungkinkan untuk menambahkan kait ke dalamnya:

app.service('myservice').hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

Kait pengaturan aplikasi

Kait penyiapan aplikasi akan menjadi bagian dari app.hooks yang sudah ada. Perbedaan untuk setup hook adalah bahwa mereka hanya menjalankan __once__ ketika app.setup() atau app.listen() dipanggil alih-alih untuk setiap panggilan layanan.

app.hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  },

  error: {
    async setup(context) {
      if(isSomeRecoverableError(context.error)) {
        restartInTenMinutes();
      }

      return context;
    }
  }
});

Perbedaan untuk setup kait

setup sedikit berbeda dari metode layanan lain dalam hal itu

  • Itu tidak bisa disebut secara eksternal
  • Itu hanya akan dipanggil sekali selama siklus hidup aplikasi
  • Itu tidak akan pernah memiliki konteks pengguna tertentu
  • Tidak memiliki params , data atau id

Ini berarti bahwa:

  • Seperti yang telah disebutkan, kait aplikasi setup hanya akan berjalan sekali, bukan untuk setiap layanan seperti kait level aplikasi lainnya
  • all hook akan __not__ diterapkan ke setup untuk menghindari putusnya hook yang ada

Semua 64 komentar

Saya telah memikirkan hal itu beberapa kali juga. Masalahnya adalah bahwa itu akan menjadi perubahan besar tetapi kita mungkin bisa masuk dengan https://github.com/feathersjs/feathers/issues/258.

@daffl #258 sangat besar. Saya telah mendapatkan bagian saya dari Hapi sebelumnya, dan itu adalah binatang yang sama sekali berbeda di bawah tenda, pekerjaan unifikasi terlihat sangat sulit. Dan inisiatif Paspor+Hapi ditinggalkan bertahun-tahun yang lalu, jadi ini adalah awal yang baru.

Untuk configure saja, hal yang dijelaskan di atas cukup mudah diterapkan dan tidak menimbulkan perubahan yang mengganggu. Hanya tidak yakin berapa banyak kasus yang akan dibahas dalam perspektif yang lebih besar.

Nah, setelah menyarankan bahwa pembuatan layanan harus mengembalikan fungsi di hari lain, untuk memberi sinyal kepada pengguna bahwa itu adalah hal yang dinamis, pemikiran selanjutnya adalah mengembalikan Janji. Jadi, apa pun urutan pembuatan layanan saat Anda menggunakan .then() Anda dapat memastikan bahwa layanan sudah diinisialisasi. Namun, Anda melakukan skema Promise itu akan menjadi lebih baik daripada yang sekarang :) Sebuah nitpick yang sangat kecil, tetapi memiliki konsekuensi besar untuk DX imo.... Referensi melingkar antara layanan adalah tempat umum.

@idibidiart Saya tidak yakin apakah saya mengerti dengan benar, apakah kode di atas sepenuhnya mencakup skenario yang Anda gambarkan?

Mungkin layanan setup dapat mengembalikan janji juga?

@bisubus jika saya memahami proposal Anda dengan benar (dan saya tahu cara Feathers dan Express), kekhawatiran saya adalah bahwa A) ini adalah perubahan yang melanggar karena fungsi app.service tidak mengembalikan janji dan B) lambat waktu startup karena Anda harus menunggu semua layanan diluncurkan sebagai lawan memulai aplikasi dan kemudian menghasilkan/menunggu hingga janji untuk layanan yang diberikan diselesaikan sebelum melanjutkan dengan permintaan layanan.... jika itu masuk akal.

@idibidiart Dalam implementasi di atas itu dilakukan untuk fungsi configure saja (di sinilah layanan biasanya didefinisikan), bukan untuk service itu sendiri. Saat ini nilai yang dikembalikan dari fungsi configure diabaikan , ini memungkinkan untuk mengembalikan sesuatu (janji) dari sana tanpa melanggar apa pun. Saya kira hal serupa dapat dilakukan dalam fungsi layanan setup , saat ini mereka tidak mengembalikan nilai , ini juga dapat digunakan.

Proposal ini dalam bentuknya yang sekarang tidak memperlambat atau merusak apa pun. Itu hanya memberikan janji inisialisasi, itu dapat dirantai untuk memastikan bahwa semuanya siap atau diabaikan. Dalam kasus saya, saya merantai app.listen(...) karena masuk akal di sana.

saya mengerti apa yang kamu maksud! Terimakasih atas klarifikasinya. Saya pikir masuk akal untuk melakukannya seperti yang Anda jelaskan termasuk mengembalikan janji dari pengaturan. Apa yang masih samar bagi saya adalah di mana Anda mengatakan bahwa app.service mengembalikan sebuah instance dari layanan. Saya menjadi tidak terdefinisi saat menjalankannya saat layanan belum siap. Anda menyarankan untuk menunggu janji penyiapan untuk diselesaikan sebelum menjalankan app.service?

@idibidiart Apakah Anda memiliki contoh dunia nyata di mana app.service(..) dapat menghadapi kondisi balapan?

Saya tidak bermaksud bahwa itu akan menunggu setup janji dalam permintaan app.service(..) , ini akan membuat app.service(..) mengembalikan janji layanan, tidak terlihat bagus.

Ya, saya punya contoh yang bisa saya gambarkan dan tunjukkan kepada Anda.

Saya tidak bermaksud menunggu janji pengaturan di app.service tetapi untuk 'menunggu' dari fungsi async (atau menggunakan generator yang setara) sebelum menjalankan metode layanan. Tidak akan memerlukan app.service untuk mengembalikan janji.

Contohnya adalah di mana layanan graphql dikonfigurasikan sebelum layanan yang bergantung padanya sehingga ketika memanggil app.service ketika layanan graphql diluncurkan, layanan tersebut belum ada.

masalahnya adalah grafik ketergantungan untuk layanan mungkin memiliki siklus (layanan A tergantung pada layanan B dan layanan B tergantung pada layanan A) jadi kami tidak dapat menjamin urutan ketergantungan linier, itulah sebabnya saya berpikir untuk menunggu penyelesaian janji pengaturan sebelumnya melanjutkan dengan permohonan app.service (sekali lagi, jika itu masuk akal!)

@idibidiart Dengan 'layanan graphql diluncurkan' maksud Anda setup metode dalam layanan graphql, bukan? Karena metode lain (CRUD) tidak akan dipanggil hingga server dimulai (dan ini dapat dijamin dengan app.toPromise().then(() => app.listen(...)) ).

Dari pemahaman saya tentang cara kerja pendaftaran layanan Feathers, layanan akan tersedia dalam urutan yang ditentukan, jadi solusinya adalah memiliki app.configure(...) dengan definisi layanan ketergantungan untuk dieksekusi lebih awal dari app.configure(...) dengan definisi layanan graphql.

Ya, hal yang Anda gambarkan biasanya ditangani dengan wadah DI. Saya sangat menyukai DI, tetapi di sini sepertinya berlebihan bagi saya. Dengan modul Node, urutan definisi layanan yang tepat dapat dipertahankan dengan relatif mudah.

Tapi Anda telah menyentuh topik yang menarik. Mungkin ada skenario di mana mungkin bermanfaat untuk memiliki janji dari beberapa layanan tertentu (dikembalikan dari layanan setup ), bukan hanya janji yang dihasilkan dari Promise.all . Saya kira ini dapat diselesaikan dengan menyimpan janji inisialisasi sebagai peta, bukan sebagai array. Dengan cara ini janji untuk layanan terpisah dapat diambil kapan saja, seperti app.service(...).toPromise().then(initializedService => ...) . Tetapi saya tidak berpikir bahwa ini dapat digunakan sebagai pengganti DI untuk menyelesaikan masalah yang Anda sebutkan, mengembalikan janji untuk layanan yang belum ditentukan akan secara diam-diam menyebabkan janji yang tertunda jika ada dependensi melingkar.

Masalahnya ada di proyek ini: https://github.com/advanwinkel/feathers-apollo-SQLite

Perhatikan bagaimana graphql dikonfigurasi sebelum layanan yang digunakan dalam resolver. Memindahkan panggilan app.service ke bagian dinamis dari resolver akan memperbaikinya. Memperbaiki urutan konfigurasi akan memperbaikinya juga.

DI terlalu setuju.

Jadi jika Anda melihat src/services/resolver.js inilah yang saya maksud:

`
// ekspor fungsi asinkron
ekspor fungsi async default Resolver(){

let app = this;

// used to have: let Posts = app.service('posts') but that returned undefined 
// because posts service was configured after the graphql service 
// So instead of that, I was thinking:

// biarkan Postingan = menunggu app.service('posts')
// yang dalam versi ini menyiratkan app.service mengembalikan janji

return {

  // Type Resolvers (type, args, context)
  User: {
    posts(user, args, context){
     // Posts will be defined whereas it would be undefined without the promise/await
      return Posts.find({
        query: {
          authorId: user.id
        }
      });
    }
  },`

Jadi di tempat lain kita akan memiliki Resolver().then(...)

Tidak yakin apa yang dilakukan setup(). Dalam hal ini, "layanan" graphql (alias graphqlExpress) tidak memiliki antarmuka layanan Feathers.

Dan yang saya maksud dengan dependensi melingkar waktu pemanggilan adalah seperti saat Penyelesai Post menggunakan layanan Pengguna dan Penyelesai Pengguna menggunakan layanan Postingan. Ini tidak akan menjadi masalah dalam hal ini karena kami menunggu di layanan Pengguna untuk diselesaikan sebelum memanggilnya dari Penyelesai Postingan dan menunggu di layanan Postingan untuk diselesaikan sebelum memanggilnya dari Penyelesai Pengguna. Jadi saya kira itu bukan ketergantungan melingkar antara layanan Posting dan Pengguna tetapi hubungan timbal balik antara resolver yang bergantung pada layanan tersebut. Maaf membingungkan.

Komentar yang diperbarui sesuai

Saya harap beberapa dari ini masuk akal secara umum meskipun saya tidak sepenuhnya memahami cara kerja di Express dan Feathers.

@idibidiart Ya, saya sudah memahami Anda dengan benar. Ini diselesaikan dengan mempertahankan urutan blok users-posts-graphql configure . Ini adalah kasus persis untuk DI, jadi tidak terlalu banyak - hanya saja tidak untuk semua orang. Sebagian besar pengembang (termasuk saya sendiri) lebih suka membuatnya tetap sederhana jika memungkinkan.

Dalam contoh yang Anda tunjukkan graphql tidak benar-benar Feathers service (mereka secara konvensional memiliki metode setup yang berisi semua kode inisialisasi), jadi tidak sesuai dengan pola yang saya jelaskan untuk setup . Ini hanya middleware yang didefinisikan dalam blok configure (bisa mengembalikan janji dari sana jika diperlukan).

Masalah dengan janji layanan yang belum didefinisikan adalah bahwa jika Graphql bergantung pada Pengguna, dan Pengguna bergantung pada Graphql (mungkin secara tidak sengaja, dan mungkin ada lebih banyak tingkatan), kita akan memiliki ketergantungan melingkar klasik. Ini akan menghasilkan janji yang tertunda tanpa kesalahan (sementara DI akan melempar kesalahan CD). Sepertinya antipattern bagi saya.

Inilah mengapa saya berpikir bahwa ini adalah pekerjaan untuk DI, bukan untuk janji saja. Akan lebih baik jika wadah DI sebagai modul opsional, tidak harus menjadi bagian inti. Jika itu dapat memanfaatkan janji inisialisasi yang ada untuk melakukan async DI (tidak ingat apakah saya pernah melihat hal seperti ini di tempat lain, tetapi itu bisa dilakukan), ini akan menjadi lebih baik.

@bisabus

Saya percaya saya berhasil membingungkan Anda sebagian. Dalam pendekatan Feathers-GraphQL, layanan Pengguna tidak akan pernah bergantung pada GraphQL, karena GraphQL adalah satu-satunya titik akhir API dan merupakan entri utama.

Apa yang saya coba selesaikan dalam contoh kode yang saya bagikan adalah mengabaikan urutan konfigurasi karena itu adalah anti-pola: Anda tidak dapat bergantung pada urutan yang linier, yaitu bahwa mereka mungkin dependensi melingkar (DI adalah solusi yang jelas di sana) tetapi tidak dalam kasus ini. Tapi itulah alasan saya tidak ingin menggunakan perbaikan yang tergantung pada urutan konfigurasi.

Dengan meminta layanan mengembalikan janji, saya tidak menyelesaikan kasus ketergantungan melingkar tetapi saya memecahkan masalah lain: urutan konfigurasi yang salah dapat menyebabkan layanan tidak terdefinisi dan itu menghadirkan misteri bagi pendatang baru yang tidak cukup berpengalaman dengan cara kerja i Express/Feathers, jadi harus menunggu janji kemudian melanjutkan dengan sisa fungsi Resolver (tanpa pemblokiran berkat tumpukan async) kami dapat memastikan bahwa Pengguna atau layanan apa pun yang bergantung pada graphQL tidak terdefinisi dan permintaan yang mungkin datang ke GraphQL sebelum layanan tersebut didefinisikan mungkin akan menyebabkan server membuat kesalahan karena Resolvers.then(...) tidak akan menyiapkan layanan GraphQL saat itu. Tetapi setidaknya saya dapat dengan mudah mengetahui apa masalahnya tanpa harus memahami hal tentang urutan konfigurasi.

Itu adalah eksperimen pikiran. Hasil pembelajaran darinya dan diskusi ini (yang memaksa saya untuk lebih memikirkannya) adalah bahwa cara paling sederhana dan termudah adalah apa yang telah diusulkan oleh @daffl dan apa yang saya temukan ketika mencoba memperbaikinya, yaitu dengan memanggil app.service( ...) dari dalam resolver, sehingga kita memiliki keadaan dinamis. Jika layanan memang memiliki ketergantungan waktu pemanggilan melingkar, yaitu atau lebih jelas dinyatakan: jika mereka memiliki 'hubungan timbal balik' dalam hal layanan A memanggil layanan B dan sebaliknya, itu tidak menjadi masalah. Jika layanan benar-benar bergantung satu sama lain dalam hal instantiasi mereka maka DI akan dibutuhkan.

@idibidiart Saya mengerti maksud Anda. Terima kasih telah menjelaskan contoh dengan Graphql, itu menambahkan beberapa detail.

Ketergantungan melingkar adalah perangkap yang paling jelas dalam situasi ini, itu sebabnya saya menyebutkannya. Juga, akan ada janji yang tertunda jika tidak ada Pengguna atau Posting sama sekali. Jika hal seperti ini harus dilakukan pada tingkat kerangka kerja, masalah ini tidak dapat diabaikan, karena janji tertunda yang tidak terduga akan menjadi PITA untuk di-debug.

Tanda yang memberi tahu bahwa janji itu tertunda adalah bahwa layanan graphql tidak akan ada ketika kami mencoba mengirimnya permintaan sebelum layanan Feathers yang bergantung padanya diselesaikan (karena fungsi Resolver async yang diekspor mengembalikan janji dan menjadi ketergantungan dari Layanan GraphQL, jadi kami tidak akan membuat layanan GraphQL sampai janji Resolver diselesaikan (sayangnya pencampuran semantik di sini) Kami dapat mengetahui layanan Feathers mana yang menunggu resolusi dengan pencatatan sederhana mengikuti setiap pernyataan 'menunggu'.

Tapi saya setuju itu bukan cara yang sangat formal untuk menangani masalah ini.

@idibidiart Situasi dengan janji yang tertunda selalu dapat ditangani dengan Bluebird .timeout() , tetapi saya akan menganggap ini sebagai peretasan.

Ya seperti yang saya katakan, tanda yang memberi tahu dalam contoh yang saya bagikan adalah bahwa layanan GraphQL tidak akan dipakai sehingga Express/Feathers akan mengembalikan 404. Mudah untuk menyimpulkan bahwa janji untuk layanan yang bergantung padanya tidak diselesaikan.

Tapi aku tidak tinggi pada pendekatan itu.

State machine deklaratif yang digerakkan oleh peristiwa dapat diatur untuk menangani urutan startup/boot-up yang paling kompleks. Jika ada minat, saya dapat mengusulkan versi imperatif berdasarkan generator ES6, yang menurut saya didukung oleh node. Saya telah melakukan orkestrasi startup kompleks semacam ini sebelumnya di UI.

@idibidiart Jika Anda memiliki sesuatu yang khusus dalam pikiran, akan menyenangkan untuk melihatnya sebagai cabang repo. Mungkin ada sesuatu yang bisa kita pikirkan.

Hanya untuk memberi tahu Anda bahwa saya memiliki masalah yang sama dan memperbaikinya dengan pendekatan yang lebih sederhana, saya mengganti metode configure() seperti ini:

app.configure = async function (fn) {
  await fn.call(this)
  return this
}

Saya juga cukup memanggil fungsi yang diberikan sebagai parameter tetapi menunggunya, sehingga Anda dapat memiliki operasi async di panggilan balik Anda. Ini berarti bahwa jika siklus hidup aplikasi Anda berurutan, Anda await app.configure() lalu app.listen() , jika tidak, Anda melanjutkan seperti sebelumnya.

Saya harap ini membantu.

@claustres Ya, ini berfungsi sebagai solusi sementara tetapi sayangnya, merusak API, app menjadi tidak dapat dirantai. Berharap kami akan menemukan sesuatu di v3.

Ya pasti itu tidak menyelesaikan masalah kompatibilitas ke belakang ...

@claustres @bisubus Akankah ada sesuatu yang menentangnya untuk menjadikannya metode terpisah configureAsync() ? Kemudian kompatibilitas mundur masih dijamin, dan memungkinkan kita untuk menggunakan pola asinkron tersebut.

Ya bisa saja. Saya juga memikirkan cara untuk membuat rantai fungsi async. Sebenarnya Anda dapat menunggu dalam ekspresi sehingga kami dapat menulis sesuatu seperti ini await (await app.configure()).configure() meskipun tidak terlalu mudah dibaca dengan banyak panggilan. Mungkin API yang dapat dirantai yang baik akan menjadi seperti ini:

.configure()
.await() // This configure is async and we wait until it finishes
.configure() // This configure is sync or async but we don't wait for it
.configure()
...

Pertanyaannya adalah bagaimana menerapkannya? Mungkin dengan menganalisis pengembalian fn.call(this) , jika itu adalah janji (async) kami menyimpannya dan panggilan berikutnya ke .await() dalam rantai akan menunggunya, selain itu berfungsi seperti biasa.

@claustres Ya, itu masalahnya. configureAsync dapat dirantai dan karenanya tidak konsisten dengan API saat ini ( await (await ... ) ... tidak dihitung). Masalah penting yang dapat diatasi oleh configureAsync adalah kontrol penuh atas janji yang ditunggu, sehingga janji tersebut dapat dirantai secara manual secara seri atau paralel. Misalnya ketika configure(bar) bergantung pada configure(foo) janji tetapi tidak pada configure(baz) . Hal yang sama berlaku untuk service . Saya berharap semuanya dapat diatasi dengan memperluas API saat ini tanpa perubahan yang merusak. Saya akan mencobanya segera.

Saya belum menambahkan ini di v3 (mungkin di 3.1) tetapi ini harus diselesaikan dengan plugin yang cukup sederhana:

module.exports = function() {
  const app = this;
  const oldConfigure = app.configure;

  app.ready = Promise.resolve();

  app.configure = function(fn) {
    app.ready = app.ready.then(() => Promise.resolve({
      callback.call(this, this);

      return this;
    }));

    return this;
  }
}

Ini akan memungkinkan untuk mengembalikan janji (atau menggunakan fungsi async ) dari panggilan balik .configure dan sebelum memulai aplikasi Anda, Anda hanya await app.ready (bagian yang sedikit rewel adalah Anda akan harus membungkus ini dalam async function yang dapat dijalankan sendiri karena Anda tidak dapat menggunakan async/await pada tingkat modul).

Saya pikir saya mengerti maksud Anda meskipun ada beberapa kesalahan ketik dalam kode Anda yang mungkin mengacaukan saya, ini mirip dengan apa yang saya pikirkan dengan await() kecuali Anda memaksa setiap konfigurasi menjadi async, simpan yang sekarang di app.ready dan perbarui dengan yang berikutnya setelah selesai. Dalam hal ini await app.ready akan meluncurkan urutan konfigurasi.

Ah, memperbaiki kesalahan. Dalam hal ini konfigurasi akan segera terjadi ketika Anda memanggil .configure tetapi secara berurutan dengan masing-masing menunggu penyelesaian sebelumnya.

@daffl Hebat. Ya, begitulah awalnya saya mendekati masalah. Ada beberapa pertimbangan yang muncul dalam diskusi panjang lebar di atas, saya sedang mengerjakan implementasinya.

Janji init disimpan secara internal, untuk tujuan debugging dan aliran kontrol yang ditingkatkan. Bisa jadi app.resolvers Peta atau lebih.

Memiliki mereka secara seri aman namun sangat tidak efisien. Ini adalah kasus yang saya temui dengan beberapa koneksi DB dan rutinitas init lainnya. Sepertinya kita memerlukan grafik ketergantungan untuk mengelola ini secara efisien. Mungkin perlu menyadari deps melingkar, tetapi selain itu cukup sederhana. Janji pemecah masalah dapat diidentifikasi di Peta dengan fungsi konfigurasinya, yaitu app.configure(foo).configure(bar, { deps: [foo] }).configure(baz, { deps: [foo, bar] }) . Ini akan diterjemahkan menjadi sesuatu seperti

Promise.all([
  resolvers.get(foo),
  resolvers.get(foo).then(() => resolvers.get(bar))
  Promise.all([resolvers.get(foo), resolvers.get(bar)]).then(() => resolvers.get(baz))
]);

Kita mungkin memerlukan fungsionalitas yang sama di service juga, karena di situlah logika bisnis biasanya berada. Dan dengan demikian mungkin melayani setup , dan dengan demikian kemungkinan app setup dan aplikasi bersarang, jadi kita berakhir dengan satu janji boot atau sesuatu.

Saya berharap ini mencakup kasus penggunaan potensial tanpa perubahan yang melanggar. Apa yang Anda pikirkan?

Jika Anda mengikat janji seperti yang saya sarankan, yang perlu Anda lakukan hanyalah meletakkan semuanya dalam urutan yang benar (misalnya, konfigurasikan koneksi db Anda sebelum apa pun yang memerlukannya). Saya tidak yakin apakah ada kebutuhan untuk membangun pohon ketergantungan (dan semua kerumitan yang menyertainya).

Jika ada hal-hal yang perlu Anda lakukan dalam urutan tertentu, Anda selalu dapat membuat janji yang Anda butuhkan secara internal sebelum mengembalikannya.

@daffl Janji

Ini mungkin lebih buruk untuk DB dan skenario lain; sedangkan untuk Mongoose, setidaknya itu mengikat janji koneksi secara internal, sehingga hal ketergantungan dapat dilewati untuk objek koneksi sebelum membuat operasi async Mongoose lainnya.

Saya akhirnya mereplikasi pola yang sama yang telah saya jelaskan di atas dengan mengelola dan merangkai janji secara manual di app.resolvers , dan saya yakin ini adalah sesuatu yang dapat ditangani oleh kerangka kerja secara deklaratif.

Yah saya setuju dengan @daffl bahwa siklus hidup aplikasi mungkin sangat kompleks (misalnya dalam lingkungan layanan mikro terdistribusi) sehingga biaya rekayasa kerangka siklus hidup generik tidak sepadan. Feathers menyediakan cara bawaan untuk mengonfigurasi aplikasi tetapi itu hanya pedoman, jika dapat mengelola fungsi async, itu akan bagus karena di node.js sebagian besar fungsi, tetapi IMHO itu sudah cukup.

Memang, jika tidak sesuai dengan kebutuhan Anda, Anda dapat membangun siklus hidup inisialisasi Anda sendiri. Saya pikir kita dapat mengambil sudut pandang di mana kita tidak berharap bahwa Feathers menyediakan siklus hidup tingkat tinggi dari aplikasi tetapi sebaliknya siklus hidup tingkat tinggi adalah khusus aplikasi dan memanggil tingkat rendah configure() ketika semuanya sudah siap di bawah tenda. Terlebih lagi jika Anda menggunakan kerangka kerja lain selain Feathers, akan sulit untuk membuat siklus hidup yang berbeda terintegrasi dalam satu Feathers.

Untuk referensi, ini - tidak seperti yang terakhir saya yang sama sekali tidak berfungsi - contoh kode lengkap:

const feathers = require('@feathersjs/feathers');

function asyncConfigure() {
  const app = this;
  const oldConfigure = app.configure;

  app.ready = Promise.resolve();

  app.configure = function(fn) {
    app.ready = app.ready.then(() => fn.call(this, this));

    return this;
  }
}

const app = feathers().configure(asyncConfigure);

app.configure(function() {
    console.log('First');
    return new Promise(resolve => setTimeout(resolve, 2000));
});

app.configure(async function() {
    console.log('Second');
});

app.ready.then(() => {
    console.log('Feathers app can be started here');
});

Anda dapat menjalankannya di sini .

Saya pikir saya mengalami hal yang sama dengan feathers-knex dan PostgreSQL.

Saya memiliki banyak layanan yang modelnya memiliki hubungan kunci asing satu sama lain yang saya buat menggunakan generator bulu. Karena db.schema.createTable adalah asinkron, urutan pembuatan tabel bisa sangat tidak dapat diandalkan, yang mengarah ke tabel yang dibuat sebelum tabel yang memiliki relasi FK.

Menggunakan plugin asyncConfigure , saya bisa mendapatkan urutan yang benar jika saya selalu mengembalikan janji dari layanan dan model. Itu tampaknya mematahkan titik akhir REST; mereka menghasilkan 404 sekarang.

Apakah model tidak mampu menangani pembuatan FK?

Apakah pengaturan layanan Anda juga ditunggu dengan benar di fungsi konfigurasi Anda jika itu async? Dan apakah penyiapan Not Found middlware dilakukan terakhir setelah semua yang lain telah disetel ?

Saya pernah mengalami masalah serupa, layanan saya diinisialisasi setelah middleware Not Found menyebabkan 404...

Terima kasih, layanan saya memang diinisialisasi setelah middleware Not Found .

Juga, file app.js dari generator memanggil app.configure(services) , dan fungsi layanan itu disebut app.configure pada setiap layanan. Memanggil services() secara langsung tampaknya memungkinkan asyncConfigure bekerja karena saya dapat menunggu app.ready.then untuk memulai middleware Not Found .

Hai semua! Anda telah mengatasi instantiasi layanan async "sampai bootstrap aplikasi" jika saya mengerti dengan benar? Bagaimana jika saya ingin membuat layanan sepenuhnya secara sporadis selama runtime? Kapan saja setelah bootstrap dalam "layanan proxy"?

Adakah peluang untuk mendapatkan solusi untuk skenario ini?

Saya memposting di Slack: Jika saya menonaktifkan middleware NotFound apakah itu akan membuat "layanan yang dibuat runtime" berfungsi (dengan mempertimbangkan semua kekurangan)?

Ya tanpa middleware Anda akan dapat menambahkan layanan baru secara dinamis saat dijalankan.

Beberapa orang melaporkan di sini https://github.com/kalisio/feathers-distributed/issues/3 bahwa menggunakan middleware NotFound sambil menambahkan layanan dinamis setelah startup aplikasi berfungsi di Feathers v3, adakah yang bisa mengonfirmasi? Terima kasih

Untuk socket tetap saja, tetapi Anda bertanya tentang REST saya kira? Saya sebenarnya punya skenario yang harus saya bahas di hari-hari berikutnya yang melibatkan ini... Akan kembali.

Ya, terima kasih atas tanggapan Anda.

Jelas TIDAK bekerja. Lepaskan app.use(express.notFound()); dan itu berhasil...

solusi saya. Lihat inti untuk async-chain.

Jangan panggil app.get('configurationConstant') di app.js saat berada di dalam rantai.
Kembalikan janji dari konfigurasi Anda untuk membuatnya asinkron.
Ingatlah untuk menunggu aplikasi siap.

const path = require('path');
const favicon = require('serve-favicon');
const compress = require('compression');
const cors = require('cors');
const helmet = require('helmet');
const chain = require('./lib/async-chain'); // https://gist.github.com/arleighdickerson/df30fd9d0fa6873983785e244f9d3b59

const feathers = require('@feathersjs/feathers');
const configuration = require('@feathersjs/configuration');
const express = require('@feathersjs/express');
const rest = require('@feathersjs/express/rest');
const socketio = require('@feathersjs/socketio');

const handler = require('@feathersjs/errors/handler');
const notFound = require('@feathersjs/errors/not-found');

const middleware = require('./middleware');
const services = require('./services');
const appHooks = require('./app.hooks');
const channels = require('./channels');

const authentication = require('./authentication');

const sequelize = require('./sequelize');
const redis = require('./redis');
const mongoose = require('./mongoose');

const config = configuration().bind(global)(); // need to bypass the chain here

// Load app configuration

const app = chain();

app.configure(configuration());

// Enable CORS, security, compression, favicon and body parsing
app.use(cors());
app.use(helmet());
app.use(compress());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(favicon(path.join(config.public, 'favicon.ico')));
// Host the public folder
app.use('/', express.static(config.public));

// Set up Plugins and providers
app.configure(rest());
app.configure(socketio());

app.configure(redis);
app.configure(sequelize);
app.configure(mongoose);

// Configure other middleware (see `middleware/index.js`)
app.configure(middleware);
app.configure(authentication);
// Set up our services (see `services/index.js`)
app.configure(services);
// Set up event channels (see channels.js)
app.configure(channels);
// Configure a middleware for 404s and the error handler
app.use(notFound());
app.use(handler());

app.hooks(appHooks);

const ready = app.chain(express(feathers()));

module.exports = new Proxy(app, {
  get(target, name) {
    return name === 'ready' ? ready : target[name];
  },
});

...

app.ready.then( app => {
  // $$$ profit $$$
})

@arleighdickerson bagaimana Anda menangani otentikasi? Saya mencoba cara Anda tetapi otentikasi (lokal, jwt) tidak berfungsi. Itu melempar Error: options.service does not exist. Make sure you are passing a valid service path or service instance and it is initialized before @feathersjs/authentication-jwt. .

@daffl Saya juga mendapatkan kesalahan pada otentikasi dengan configure Anda yang dimodifikasi. Dalam hal ini ia melempar Unhandled Rejection at: Promise Promise .... Cannot read property 'hooks' of undefined , karena app.services('authentication') tidak terdefinisi.
Bagaimana Anda membuatnya bekerja dengan otentikasi?

Jika itu penting, saya menggunakan feathers-knex , dan project/services/authentication dibuat dengan generator.

Situasi saya sama seperti yang dijelaskan @mwojtul , db.schema.createTable adalah asinkron dan jika model tidak dibuat dalam urutan tertentu, referensi dibuat sebelum tabel tempat mereka bergantung, dll.

Apakah ada ketentuan untuk menyediakan implementasi untuk ini di inti? Juga, beberapa informasi tentang ini dapat ditambahkan dalam dokumentasi, saya dapat membuat PR ketika saya membuatnya berfungsi.

halo - saya punya contoh otentikasi yang berfungsi. tolong beri tahu saya jika
Anda tertarik. itu tidak mudah untuk mendapatkan pekerjaan, imho. saya mempunyai
baik google & github contoh yang sangat sederhana.

Terima kasih,

Mark Edwards

Pada hari Minggu, 15 April 2018 pukul 06:16, Albert Casanovas [email protected]
menulis:

@arleighdickerson https://github.com/arleighdickerson bagaimana kabarmu?
menangani otentikasi? Saya mencoba cara Anda tetapi otentikasi (lokal, jwt)
tidak bekerja. Itu berteriak Kesalahan: options.service tidak ada. Yakinkan
Anda melewati jalur layanan atau instance layanan yang valid dan itu adalah
diinisialisasi sebelum @feathersjs/authentication-jwt..

@daffl https://github.com/daffl Saya juga mendapatkan kesalahan pada
otentikasi dengan konfigurasi Anda yang dimodifikasi. Dalam hal ini ia berteriak Tidak Tertangani
Penolakan di: Janji Janji .... Tidak dapat membaca properti 'kait' yang tidak ditentukan,
karena app.services('otentikasi') tidak ditentukan.
Bagaimana Anda membuatnya bekerja dengan otentikasi?

Jika itu penting, saya menggunakan feathers-knex, dan proyek/layanan/otentikasi
dibuat dengan generator.

Situasi saya sama dengan @mwojtul https://github.com/mwojtul
menjelaskan, db.schema.createTable async dan jika modelnya tidak
dibuat dalam urutan tertentu referensi dibuat sebelum tabel mereka
tergantung, dll.

Apakah ada ketentuan untuk menyediakan implementasi untuk ini di
inti? Juga, beberapa informasi tentang ini dapat ditambahkan di
dokumentasi, saya bisa membuat PR ketika saya membuatnya bekerja.


Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-381405820 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ACyd4oxRps-7QXxBPSs5fDHTupIgIqkiks5to0g9gaJpZM4L3ppM
.

@edwardsmarkf itu akan sangat membantu!

berhati-hatilah dengan apa yang kamu minta:

https://github.com/edwardsmarkf/fastfeathers

github-login dan gmail-login adalah yang terpendek. tujuan saya adalah memiliki
skrip bash yang dapat dijalankan (atau dipotong/ditempel) yang memanfaatkan sepenuhnya
bulu-CLI untuk meminimalkan upaya.

jsgrid-sequelize menggunakan js-grid yang benar-benar luar biasa sebagai front-end untuk
ilustrasi saja. saya telah mengujinya terhadap mariaDB, cockroachDB,
postgresql, dan timescaleDB, dan semoga google-spanner segera.

saya akan mulai bekerja di https://fastfeathers.site segera dan memiliki semua ini
tersedia.

tolong beritahu saya bagaimana menurut anda.

Terima kasih,

Mark Edwards

Pada Sun, Apr 15, 2018 at 05:07, Albert Casanovas [email protected]
menulis:

@edwardsmarkf https://github.com/edwardsmarkf itu akan sangat
bermanfaat!


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-381449095 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ACyd4o245q0lc1Uzg63eroDDEJFTAMEmks5to-CtgaJpZM4L3ppM
.

@edwardsmarkf terima kasih telah membagikan proyek Anda, Anda dengan jelas wink:. Saya memeriksa file *-login tetapi saya tidak melihat bagaimana Anda memecahkan masalah otentikasi yang saya alami.

terima kasih telah berbagi proyek Anda, Anda jelas meluangkan waktu untuk itu

ahhh pujian cinta! cintai mereka!

Saya tidak melihat bagaimana Anda memecahkan masalah otentikasi yang saya alami.

maaf tentang itu. tolong beri tahu saya apa yang Anda temukan.

saya berharap bahwa kita akan melihat semacam "buku masak bulu" untuk semua
masalah umum. bulu (seperti semua kerangka kerja) membuatnya terlihat cukup mudah untuk
memulai, tetapi satu dengan cepat mengalami masalah. dan tentu saja bulu adalah
masih cukup baru.

ketika saya pertama kali mulai dengan bulu, mereka berada dalam transisi antara auk
dan buzzard, dan tidak ada contoh yang berfungsi. saya ingin membantu
orang-orang yang datang setelah saya untuk masuk ke bulu, dan itulah sebabnya saya mulai
bulu cepat. selalu lebih mudah untuk belajar dan mengeksplorasi sesuatu yang
sudah bekerja, imho.

terima kasih telah menulis kembali.

Terima kasih,

Mark Edwards

Pada Selasa, 17 April 2018 pukul 22:57, Albert Casanovas < [email protected]

menulis:

@edwardsmarkf https://github.com/edwardsmarkf terima kasih telah berbagi
proyek, Anda jelas menghabiskan beberapa jam di sana . Saya memeriksa *-login
file tetapi saya tidak melihat bagaimana Anda memecahkan masalah otentikasi saya
memiliki.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/feathersjs/feathers/issues/509#issuecomment-382271180 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/ACyd4nD9E0OPUickI7KOS-423Rfl8oYRks5tptW2gaJpZM4L3ppM
.

Ini telah menjadi diskusi yang cukup panjang tetapi inilah proposal untuk menyelesaikan masalah pengaturan aplikasi asinkron. Ternyata, Feathers sudah memiliki cara yang cukup mapan untuk menangani alur kerja metode asinkron, yaitu Hooks . Berdasarkan #924 oleh @bertho-zero untuk memungkinkan penambahan fungsionalitas hook ke metode asinkron apa pun, kami sekarang memiliki cara untuk membuat pengaturan aplikasi atau layanan tertentu menjadi lebih mudah.

Asinkron service.setup , app.setup dan app.listen

Ini akan menjadi perubahan utama yang menjadikannya inti Feathers ( @feathersjs/feathers ) versi 4. service.setup , app.setup dan app.listen akan mengembalikan Janji dan berjalan secara tidak sinkron.

__Sebelum:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');
const server = app.listen(port);

server.on('listening', () =>
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
);

__Sekarang:__

const logger = require('./logger');
const app = require('./app');
const port = app.get('port');

app.listen(port).then(server => {
  logger.info('Feathers application started on http://%s:%d', app.get('host'), port)
});

Kait pengaturan layanan

Juga dilacak di https://github.com/feathersjs/feathers/issues/853 , service.setup sekarang akan menjadi asinkron (kembalikan janji) yang memungkinkan untuk menambahkan kait ke dalamnya:

app.service('myservice').hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

Kait pengaturan aplikasi

Kait penyiapan aplikasi akan menjadi bagian dari app.hooks yang sudah ada. Perbedaan untuk setup hook adalah bahwa mereka hanya menjalankan __once__ ketika app.setup() atau app.listen() dipanggil alih-alih untuk setiap panggilan layanan.

app.hooks({
  before: {
    async setup(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  },

  error: {
    async setup(context) {
      if(isSomeRecoverableError(context.error)) {
        restartInTenMinutes();
      }

      return context;
    }
  }
});

Perbedaan untuk setup kait

setup sedikit berbeda dari metode layanan lain dalam hal itu

  • Itu tidak bisa disebut secara eksternal
  • Itu hanya akan dipanggil sekali selama siklus hidup aplikasi
  • Itu tidak akan pernah memiliki konteks pengguna tertentu
  • Tidak memiliki params , data atau id

Ini berarti bahwa:

  • Seperti yang telah disebutkan, kait aplikasi setup hanya akan berjalan sekali, bukan untuk setiap layanan seperti kait level aplikasi lainnya
  • all hook akan __not__ diterapkan ke setup untuk menghindari putusnya hook yang ada

Hai, berita bagus dan terima kasih atas tanggapan terperincinya.
Apakah ini berarti bahwa kita tidak memerlukan solusi "nonaktifkan middleware NotFound" lagi?

Menanganinya menggunakan kait tampaknya bagus meskipun saya akan menambahkan jenis kait baru untuk menangani kasus penggunaan ini (mis. kait pengaturan). Memang menggunakan kait biasa menimbulkan kebingungan IMHO karena tampaknya berfungsi seperti kait lain tetapi sebenarnya tidak ...

Inisiatif hook terlihat menarik. Sepertinya tidak ada dukungan janji untuk middlewares. Apakah middleware seharusnya dibungkus dengan layanan dummy untuk mengaktifkan janji?

Fungsionalitas ini tidak akan memengaruhi cara middleware berjalan. Dimungkinkan untuk mendaftarkan middleware di hook setup setelah hal-hal asinkron lainnya terjadi:

setup: [ doAsyncStuff, context => context.app.use(errorHandler) ]

@claustres Saya juga memperdebatkan itu, sesuatu yang terlihat seperti:

app.service('myservice').hooks({
  before: {},
  after: {},
  error: {},
  setup: {
    before(context) {
      await createDatabaseTableIfNotExists();

      return context;
    }
  }
});

app.hooks({
  before: {},
  after: {},
  error: {},
  setup: {
    before: createDatabaseTableIfNotExists(),
    after: startMonitoring(),
    error: restartInTenMinutes()
  }
});

Itu menambahkan beberapa ketidakkonsistenan dalam hal mengambil dan mendaftarkan kait dan melakukan codemods tetapi lebih eksplisit daripada memiliki beberapa pengecualian internal yang harus Anda ketahui.

@daffl Saya punya pertanyaan tentang cara baru menangani pengaturan ini: apa yang harus dilakukan dalam metode pengaturan dan apa yang harus dilakukan?

Saya pikir sama dengan metode layanan. Apa pun yang dibutuhkan layanan untuk bekerja dengan sendirinya harus diimplementasikan dalam setup . Apa pun yang dibutuhkan aplikasi untuk menyiapkan layanan harus ditambahkan melalui kait. Saya juga baru menyadari bahwa harus ada mixin layanan yang menambahkan metode setup jika tidak ada, jika tidak maka pengait akan meledak.

Hanya untuk memastikan: Apakah ini secara resmi mendukung pembuatan layanan saat runtime kapan saja setelah memulai aplikasi (dalam kasus saya dipicu pengguna)?

Ini sudah berfungsi untuk soket web dan lebih merupakan batasan Express daripada batasan Feathers. Anda dapat membuat penangan notFound diperbarui untuk didaftarkan _setelah_ semua middleware khusus tetapi _sebelum_ layanan Anda dan memintanya memeriksa URL terhadap jalur di app.services dan hanya membuang kesalahan NotFound jika tidak ada yang cocok .

Apakah ini bekerja? Dokumentasi pengaturan menunjukkan bahwa itu mengembalikan janji. Akankah aplikasi menunggu janji atau haruskah kita menggunakan kait seperti yang ditunjukkan di sini. Karena dokumentasi kait tidak menunjukkan kait apa pun untuk penyetelan.

Apa, saat ini, cara yang tepat untuk membuat penyiapan menunggu?

Saya pikir tidak ada pengembalian janji di app.configure(), itulah penyebab awal masalah ini. Tidak ada cara untuk membatalkan jika terjadi kegagalan selama konfigurasi (mis. database tidak tersedia)

Apa status masalah ini?
Saya menggunakan versi 4.3.0-pre.2 dan metode service.setup tampaknya belum asinkron.

Ini akan menjadi bagian dari v5 menggunakan sistem kait baru (https://github.com/feathersjs/feathers/issues/932).

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

perminder-klair picture perminder-klair  ·  3Komentar

jordanbtucker picture jordanbtucker  ·  4Komentar

rrubio picture rrubio  ·  4Komentar

rstegg picture rstegg  ·  3Komentar

corymsmith picture corymsmith  ·  4Komentar