Hacker News new | ask | show | jobs
by vidarh 841 days ago
My approach to what they are doing in my last job was to augment our models with more fine-grained information than what the database schema held, as well as with metadata about which views made sense or were needed, and then output schema information in our API. The raw base case was generated directly from the live database schema, so even without a line of code you had something once you'd enabled it (default off for anyone but people with devops level admin rights because giving a default on view of a raw table is a recipe for leaking data), and most of what the models provided were just more structured forms of what the models already needed, such as validations.

Then we built a set of generic frontend components for that in React once.

And we then had an instant UI for 50+ models, but the frontend devs could trivially selectively override both the whole page for models where the default was too simplistic, or how to render specific types of fields or for specific fields of specific models.

The "instant UI" was flexible enough that for most "backend" views we never customised it on the frontend. But being able to have the frontend devs customise the user visible things was essential, and thanks to having the ability to replace things bit by bit they could focus on the things that didn't look or work right, instead of "everything". And a lot of their work then made it back into augmenting the backend with info to let the generic frontend apply those improved views elsewhere as well.

I have only had a cursory look at FastUI. If they do something similar to that, then great. If they actually try to have the backend generate all or most of the UI and serve that up instead of serving up metadata, then I fully agree with your issues.

2 comments

So, how did the “override” work? What made it easier to replace a part not whole page without understanding how the framework worked?

Agree completely that is the only sane goal - just wondering how you got there?

"The framework" in this case was a very thin layer underneath a bunch of components for which one of the inputs was a prop that included the relevant part of the schema from the server. So at a top level you got everything. The default top-level component knew to look up a view definition, and type, and a function that knew how to render that type, and that was pretty much it.

Then it'd just apply that recursively. For a simple model there'd only be two levels: The view, containing a bunch of columns, and the columns themselves, and the view type knew to iterate over the columns in the view and look up their type and tell them to render themselves. For more complex views frontend devs might e.g. provide a custom view type that'd group columns based on certain attributes, but still delegate to the generic mechanism to render those columns.

So replacing a part just meant writing a component that knew how to render itself from its props, just like any other component. Just that some of those props would be coming from the backend.

We'd generally try to avoid replacing components if the only reason for a component was to override how something was rendered, and to focus on why it should be rendered differently and consider if we could reflect that 'why' in the data, or other ways.

E.g. were certain fields grouped together because they were semantically related? Then reflect that with an attribute in the "meta schema" for those columns, and make the frontend component render related groups according to the type of that group (let's say a bunch of fields reflects a user address). Incidentally this tended to be very simple, as with a relational model when there were groups like that the right thing was often for them to be a separate table/model anyway, and we 'just' needed to distinguish between type handlers for rendering an object 'as the page' vs. 'as a reference' vs. 'as a group within the page', and that was easy enough to distinguish "well enough".

At a top level, we'd look for a registered view-level component (or fall back on the default), within a view if the backend returned the model instance "in-line" we rendered it as a group, and if it returned an id, we'd render it as a reference.

So a "User" object for example would render as a profile view if it was not your user and you went to a page where the associated API call returned the User as the top level model, you personalised profile page w/optional edit view if it was your user, or a "pill" linking to the user profile if it was just a reference.

The idea was that frontend components should focus on how to render specific types of data in a specific context, rather than specifying how specifically to render the page, because it'd make no sense to have the frontend e.g. try to render a field that no longer exists, or try to render a field as a different type it is when the meta data about which fields were available and what type they are was already available.

That's the way I've been doing things for a long time as a freelancer also.

Especially for business oriented apps, most of the models only need a list view with a table, and a detail view displaying some properties and related lists of models.

It works wonderfully.