-
Notifications
You must be signed in to change notification settings - Fork 23
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
@Scaffold annotation for Controllers and Services #118
Conversation
Looks awesome! Is using: @ScaffoldService(GenericService<User>)
class UserService
{} equivalent to: class UserService extends GenericService<User>
{} It would be really nice if there were some tests showing that these new features work as intended. |
yes, it is equivalent more or less. It works the same way how The main work that it does is by setting the domain class in the constructor. If you used class extension, you would have to create a constructor for every service.. class UserService extends GenericService<User> {
UserService() {
super(User, false)
}
} would more or less be the equivalent. I am trying to decide whether or not to ditch the |
I don't follow, why would I have to create a constructor (if my GenericService has a no-args constructor)? |
If your GenericService has a no-args constructor, how else would you resolve as a User if you don't have a reference to T.class? class GenericService<T> {
@Autowired DatastoreService datastore
GenericService() {}
T get(Serializable id) {
// how else would you resolve T ???
}
} Doesn't @ScaffoldService(GenericService<User>)
class UserService {} vs class UserService extends GenericService<User> {
UserService() {
super(User, false)
}
} class GenericService<T> {
Class<T> resource
String resourceName
@Autowired DatastoreService datastore
GenericService(Class<T> resource, boolean readOnly) {
this.resource = resource
this.readOnly = readOnly
}
T get(Serializable id) {
T instance = resource.getDeclaredConstructor().newInstance()
instance.properties << datastore.get(id)
instance
}
yes, readOnly. |
Can't you get the implementation class with T get(Serializable id) {
T instance = (T) getClass().getDeclaredConstructor().newInstance()
instance.properties << datastore.get(id)
instance
} |
Isn't the whole point to instantiate and return a |
Yes, you are correct! Thinking mistake by me 😄 |
All good. You almost had be believing you when I read your code at 2am lol |
@matrei @jamesfredley I am getting close to merging this. One last thing we could consider: They both do the same thing:
@Scaffold(GenericService<User>)
class UserService {} @Scaffold(RestfulController<User>)
class UserController {} @Scaffold(domain = Book)
class BookController {} |
@codeconsole If it is technically feasible, I think a unified Should we also deprecate (not remove), the usage of |
And we will want to update https://github.com/grails/grails-doc/blob/6.2.x/src/en/guide/scaffolding.adoc |
Provided default @Scaffold(domain = User)
class UserService {}
@Scaffold(domain = Book)
class BookService {
Book save(Book book) {
book.owner = request.user
super.save(user)
}
}
@Scaffold(domain = Car)
class CarService {}
@Scaffold(domain = Pet, readOnly = true)
class PetService {}
class BootStrap {
CarrService carService
PetService petService
UserService userService
BookService bookService
def init = { servletContext ->
User rainboyan = userService.save(new User(username:'rainboyan')
Car porsche = carService.save(new Car(make:'Porsche', model:'911', owner: rainboyan)
Book grails = bookService.save(new Book(name:'Programming Grails')
Pet cat = petService.save(new Pet(name: 'Garfield', owner: rainboyan) // exception throw because service is readOnly
}
} |
Created a version of RestfulController that utilizes services so that a complete implementation could be provided that uses a scaffolded service. @Scaffold(RestfulServiceController<Book>)
class BookController {} @Scaffold(domain = Book)
class BookService {} |
Could we compile And don't we also have to manage the package grails.plugin.scaffolding
import grails.artefact.Artefact
import grails.gorm.api.GormAllOperations
import grails.gorm.transactions.ReadOnly
import grails.gorm.transactions.Transactional
import grails.util.GrailsNameUtils
import groovy.transform.CompileStatic
@Artefact("Service")
@ReadOnly
@CompileStatic
class GenericService<T extends GormEntity<T>> {
GormAllOperations<T> resource
String resourceName
String resourceClassName
boolean readOnly
GenericService(Class<T> resourceClass, boolean readOnly) {
this.resource = resourceClass.getDeclaredConstructor().newInstance() as GormAllOperations<T>
this.readOnly = readOnly
this.resourceClassName = resourceClass.simpleName
this.resourceName = GrailsNameUtils.getPropertyName(resourceClass)
}
protected T queryForResource(Serializable id) {
resource.get(id)
}
T get(Serializable id) {
queryForResource(id)
}
List<T> list(Map args) {
resource.list(args)
}
Long count() {
resource.count()
}
@Transactional
void delete(Serializable id) {
if (readOnly) {
return
}
resource.delete(queryForResource(id), [flush: true])
}
@Transactional
T save(T instance) {
if (readOnly) {
return instance
}
resource.save(instance, [flush: true])
}
} |
Sure, should we rename |
Maybe |
but is this the best name since it could also be utilized independently like RestfulController can? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GormService is a unique name across the Grails codebase. 👍
Issue created for documentation update: grails/grails-doc#909
Adds support for the annotation
@Scaffold
which provides an alternative to thestatic scaffold = Domain
approach.It also empowers the developer to use their own base controller instead of being forced to use
RestController
which could be very limiting.RestController
does not encapsulate all necessary business logic and is completely divergent from the Service MVC model that is generated by this plugin. With this annotation, developers can now use an extended RestController to fit their needs, or create an entirely different generic controller that is not related at all to RestController and more closely matches the type of controller that is generated by this plugin.can now be written as this
Introduce
@Scaffold
Service supportIt is very powerful if you have common business logic shared across multiple or all services and use a service layer. It works similar to how scaffold controllers work. You have a super class that you define (such are
RestfulContoller
for scaffold controllers) and then any Services with the annotation get that super class injected.Now all I have to do is have define a Service with the annotation and all those methods become implemented. Unlike
@Service
, I can override individual methods and call superIntroduced 2 new classes:
RestfulServiceController
- which is the same as RestfulContoller, but makes all datasource calls to the respective Service.GormService
- which scaffolds a generic service similar toService.groovy
but instead of using an interface with@Service
, it sets a superclass where methods can be overridden and extended. You can also define any class as your base service.By using
@Scaffold
you can now do things for specific needs of groups of domain objects.The possibilities are endless!
#113
#114