Repository navigation
Replies: 1 comment 3 replies
|
I'd go with neither as written. Keep Option 1 puts two APIs into one hook chain. The schemas have to accept the union of both apps, and the access rules hang on a Option 2 is right about the internal service but goes too far with the methods. Where I draw the line: a different audience or response shape gets its own service, a different list is a query parameter allowed by that service's query schema, and only real actions become custom methods. Ship or cancel can often be a // orders: the data service, server-side only
app.use('orders', new OrderService(getOptions(app)), { methods: [] })
app.service('orders').publish(() => []) // methods: [] hides methods, not events
// my/orders: the customer app
class MyOrdersService {
constructor(private app: Application) {}
// only pass the query on, so the inner call is a plain internal call
async find(params: MyOrderParams) {
return this.app.service('orders').find({ query: params.query })
}
async get(id: Id, params: MyOrderParams) {
return this.app.service('orders').get(id, { query: params.query })
}
async create(data: OrderData) {
return this.app.service('orders').create(data)
}
}
app.use('my/orders', new MyOrdersService(app), { methods: ['find', 'get', 'create'] })
app.service('my/orders').hooks({
around: {
all: [
authenticate('jwt'),
schemaHooks.resolveExternal(myOrderExternalResolver), // drops internal fields
schemaHooks.resolveResult(myOrderResolver) // virtual tracking + messages
]
},
before: {
all: [
schemaHooks.validateQuery(myOrderQueryValidator), // _id, customerId, status, $sort, $limit...
schemaHooks.resolveQuery(
resolve<MyOrderQuery, HookContext>({
// whatever the client sends, it only ever sees its own orders
customerId: async (_value, _query, context) => context.params.user._id
})
)
],
create: [
schemaHooks.validateData(cartValidator), // { lines: [{ productId, qty }] }, nothing else
schemaHooks.resolveData(cartResolver, totalResolver) // prices from the catalog, customerId, status
]
}
})
// staff/orders: same pattern, its own query schema and rules
class StaffOrdersService {
constructor(private app: Application) {}
async find(params: StaffOrderParams) {
return this.app.service('orders').find({ query: params.query })
}
// ...get, create, patch
async cancel(data: { id: string; reason: string }, params: Params) {
// refund, restock, notify...
return this.app.service('orders').patch(data.id, { status: 'cancelled' })
}
}
app.use('staff/orders', new StaffOrdersService(app), {
methods: ['find', 'get', 'create', 'patch', 'cancel']
})
app.service('staff/orders').hooks({
around: {
all: [authenticate('jwt'), requireStaff, schemaHooks.resolveExternal(staffOrderExternalResolver)]
},
before: {
find: [
schemaHooks.validateQuery(staffOrderQueryValidator), // status, assignedTo, deliveryDate ($gte/$lte)...
schemaHooks.resolveQuery(
resolve<StaffOrderQuery, HookContext>({
// GET /staff/orders?assignedTo=me&status=paid
assignedTo: async (value, _query, context) => (value === 'me' ? context.params.user._id : value)
})
)
]
}
})The fe2 overview is then just Two things to watch with this layout:
With several services like this, the wrapper classes are small enough to share a base class. |
Uh oh!
There was an error while loading. Please reload this page.
Hello, first of all, thanks for this awesome framework. I've been using it for a while and have a design question I can't settle on.
I have one Feathers v5 backend with an
ordersservice on MongoDB:Two frontends use it differently:
Option 1: keep standard CRUD and handle the cases in hooks:
Option 2: keep the CRUD service internal and expose one external service with explicit custom methods:
Note: I've intentionally left out schemas, query validation, resolvers etc. to keep the examples as abstract as possible.
Option 1 is simpler, but the hooks keep growing with every new case. Option 2 gives clearer contracts, but means more methods to maintain and moving away from the standard CRUD interface.
Both frontends are expected to grow and drift further apart over time, but they will always be built on the same
Ordermodel and collection. Andordersis just one example, there are quite a few services like this.I searched around a bit and found #1976 (pretty old though), which says that around 90% of use cases can be handled with the standard CRUD interface and hooks, and custom methods are for the rest. I'm wondering whether my case is one of those edge cases, or whether I'm overcomplicating it and it still fits CRUD + hooks.
Which way would you go in Feathers, and where do you draw the line?
Thanks!
All reactions