Develop by using AL objects
The object model
AL objects fall into a few families: data (table, tableextension), UI (page, pageextension, pagecustomization, profile), logic (codeunit, report, reportextension, query, xmlport), metadata (enum, enumextension, permissionset, permissionsetextension, interface, controladdin) and integration (page of type API).
Tables: every field needs a DataClassification (mandatory; ToBeClassified is not acceptable for AppSource). Keys define the primary key and the access path. A TableExtension adds fields to a base table and reuses the base table name.
Pages: PageType matters (Card, List, Document, API, RoleCenter, Worksheet, ListPart). SourceTable plus Editable, InsertAllowed, ModifyAllowed and DeleteAllowed control what a user can do.
PageExtension: place controls with addlast, addfirst, addafter, addbefore, or change existing ones with modify. You can add fields inside a modified group; you cannot delete base controls.
Naming: AppSource requires an affix (prefix or suffix). Partners can enforce one for their PTEs with mandatoryAffix or mandatoryPrefix in app.json - every object name must then contain it.
Access: internal objects cannot be referenced from another extension. Access = Internal is the default for many object types; set Public to expose them.
Rule of thumb for adding a field end to end: tableextension (field + DataClassification) -> pageextension (addlast) -> permissionsetextension -> an upgrade codeunit if existing data needs populating.
Traps: trying to add a field with a page extension alone (the field does not exist until a table extension declares it); extending an enum by creating a new enum instead of an enumextension; assuming modify can remove a base control; forgetting DataClassification on extension fields.