Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Options generation annotations on entities #26007

Open
Tcharl opened this issue Apr 30, 2024 · 3 comments
Open

Options generation annotations on entities #26007

Tcharl opened this issue Apr 30, 2024 · 3 comments

Comments

@Tcharl
Copy link
Contributor

Tcharl commented Apr 30, 2024

Overview of the feature request

The goal would be to add some jdl annotations and cli options on entities.

The goal would be to split annotations into two categories: layer ones vs C-R-U-D ones.

-- Frontend --

  • @Form would generate frontend forms
  • @FrontClient would generate client-side consumer no matter the consumption strategy

-- Controllers --

  • @RestController would use api
  • @SSEController would use streaming (listeners and senders)
  • @StreamConsumer(<topics>) would Consume message via streams (data coming from other microservice)

-- Services --

  • @RestClient(<toApplication>/<toEntity>) would call the target service api instead of repository in the service implementation.
  • @StreamProducer(<topics>) would hook service.save() and send messages in the topics instead of using repositories
  • @RepositoryConsumerwould use usual spring data providers

-- Data consumption --

  • @Repository would generate only entity and spring data repository for an entity.

-- C-R-U-D --

Same logic to be applied to methods (create:update/patch/get/delete), decorelated to layers.

--- summing up
As such, 'reasonable default' as per Inclusive property rfc would be:

@Form
@FrontClient
@RestController
@RepositoryConsumer
@Repository
entity <name>{ ... }

and for crud:

// Won't generate the 'delete button', neither the methods
@CREATE
@GET
@GETALL
@UPDATE
entity <name>{ ... }

And to let user be more precise by combining both (overriding class-level choices):

@Form(methods=[@GET,@UPDATE]) // this choice won't generate the 'creation' form
@FrontClient
@RestController
@RepositoryConsumer
@Repository
@CREATE
@GET
@UPDATE
entity <name>{ ... }

Motivation for or Use Case

The goal would be to provide much more control on entities generation

The ultimate goal being to provide a versatile solution for complex applications with few effort from the end user (plumbery jhipster-managed), i.e.

application clientCartPortal {
  entities: PurchaseRequest
  type: gateway
}
application supplySink {
  entities: Supply,Order
  type: microservice
}
application supplyOperatorPortal {
  entities: Shipping,OrderStatus
  type: gateway
}

@Form
@FrontClient
@RestController
@StreamProducer(topics=[orders])
@CREATE
@UPDATE
@GET
entity PurchaseRequest extends Supply {} // can just replicate the fields while extensions not supported

@StreamConsumer(topics=[orders])
@RepositoryConsumer
@StreamProducer(topics=[shipped]) // user will just have to filter element with status 'shipped' to the stream
@Repository
entity Supply {
  quantity int
  itemId UUID // or relations to stock.item
  unitprice float
}

entity OrderStatus {
  status: enum [Waiting,Processing,Shipped]
}

oneToMany {
Supply{status} to OrderStatus{supplies}
}

@Form
@FrontClient
@StreamConsumer(topics=[supplies])
@SSEController
@GETALL
entity Shipping extends Supply {} // shipping portal will receive updates ;-)

I want to contribute to this feature, however, seems huge so need some workforce help + bounties please (to motivate other contributors)!
@qmonmert @mshima @mraible @MathieuAA wdyt ?

  • [ x ] Checking this box is mandatory (this is just to show you read everything)
@MathieuAA
Copy link
Member

Hello everyone, while I don't doubt it's doable right now, there may first have to be a design phase so as to make the necessary adjustments to the parsing system, then another change in the generator.
The JDL parsing system identifies what's relevant, map and store it in some way, then creates objects (different from JHipster object shapes) based on what has been previously read.
Why it is relevant: this intermediary step is necessary so as not to be tightly bound to the generator. And yes, this requires a last mapping to get JHipster shapes, this is by design and should not be changed IMO.
What happens without it: one makes changes to the generator system, then makes a similar change to the parsing system (here be binding). Now say one wants to change the generator in some fashion, and forgets to change the parsing system accordingly. The next time someone wants to change both, there will be a difference between the two and a confusion: what version is right? (That's why tests are important.)

To sum up, to bind every annotation to a specific generation, my opinion is that one has to: make a change to the parsing system to acknowledge said change, wire it to the generator with an explicit mapping (we leave the parsing system to enter the generator system, this gate matters), and then read the output of this mapping to generate what's wanted.

@mshima
Copy link
Member

mshima commented May 20, 2024

I think entity does not apply to this.
Maybe it should be model.
entity would be a shortcut for model with many optionals.

We currently have support for @EntityDomainLayer(false), @EntityRestLayer(false), @EntityPersistenceLayer(false), @EntitySearchLayer(false)

@Tcharl
Copy link
Contributor Author

Tcharl commented May 20, 2024

Hi @mshima , wasn't aware about this concept, totally make sense!

Deny vs allow

First question: do you know if the current supported annotations are operationally used or beta? According to RFC, reasonable default should be used if nothing is specified, otherwise any annotation superseeds default.

Thus, specifying

entity foo {}

would be equals to

@EntityForm
@EntityFrontRestClient // or @EntityClient(commandproto=rest, queryproto=rest) or @EntityClient(proto=rest) or @EntityClient being equals to @EntityClient(proto=rest) because it's the reasonable frontend client default if no additional flag is specified
@EntityRestLayer // would prefer producer for consistency
@EntityService // (service=impl) by default, compared to serviceClass
@EntityDTO
@EntityPersistenceLayer
@EntityDomainLayer
entity foo {}

Meaning that the (false) option shouldn't exist: IMHO allowlist provides much more capabilities/flexibility than denylist

DSL

For the model vs entity, I definitely love the idea for a different reason.

From what I understand, there's a decoupling between the model (description of the class, logical layer) and the entity which is the real instance of the class.

Let's illustrate pseudocoding:

application operatorPortal {
  name: operator
  type: sql
  entities: Supply, ShippingStatus
}
application customerPortal {
  name: customer
  type: elastic
  entities: Order, PaymentStatus, CatalogItem
}

model Goods { // So called 'interchange format', things that is common between provider and consumer, maybe even frontend components
  itemId: UUID
  quantity: number
  name: string
  address: string
}

@EntityForm
@EntityFrontRestClient
@EntityRestLayer // would prefer producer for consistency
@EntityService(interface=true) // compared to serviceClass
@EntityDTO
@RestClient(targetApplication=operatorPortal, targetEntity=Supply)
entity Order model Goods { // Only model (Goods) attributes will be used for transfert
  status: PaymentStatus
}

@EntityForm
@EntityFrontRestClient
@EntityRestLayer // would prefer producer for consistency
@EntityService(interface=true) // compared to serviceClass
@EntityDTO
@EntityPersistenceLayer
@EntityDomainLayer
@SearchLayer
entity CatalogItem model Goods {
  @Displayed(false)   itemId: UUID // overrides the model behaviour
  picture: blob
}


@EntityRestLayer // would prefer producer for consistency
@EntityService(interface=true) // compared to serviceClass
@EntityDTO
@EntityPersistenceLayer
@EntityDomainLayer
entity Supply model Goods {
  status: ShippingStatus
}

Any remark? Agrees? What about a spec defining getall, getone, update and create for each option (no real idea about a proper syntax)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants