Our architecture, originally built with Grails 3.3.x semi-monoliths, is currently leaning toward using more Micronaut functions in…
How to Share GORM Domain Classes between Grails and Micronaut

Our architecture, originally built with Grails 3.3.x semi-monoliths, is currently leaning toward using more Micronaut functions in situations where it makes much more sense such as jobs, queue consumers or AWS DynamoDB triggers. The biggest obstacle seems to be a handful of GORM domain classes which spreads across Grails application and which blocks extracting logic into Micronaut functions. Let’s take a look at how to share domain classes between Grails and Micronaut applications.
In theory, you can use GORM without Grails. Actually Micronaut comes with out-of-the-box support of GORM, so it seemed to be quite a simple task. Basically, you just copy & paste your domain class and add @Entity annotation. This works great for standalone applications and Micronaut but then you will have many difficulties including the domain classes into Grails. The only suitable way I have figured out is to let the library pretend to be a Grails plugin but without including unnecessary dependencies or using any Grails Gradle plugins.
Mimicking Grails Plugin
Luckily, to generate Grails plugin descriptor you only need to include Grails core dependency on the compile classpath and have any Groovy class with GrailsPlugin suffix. Add the following dependencies into your Gradle file:
dependencies { _// GORM_ compile "org.grails:grails-datastore-gorm-hibernate5:$gormVersion"
// more flexible binding support (known from controllers),
// but adds too many dependencies
// compile "org.grails:grails-web-databinding:$grailsVersion"
_// required for Grails Plugin generation_ compileOnly "org.grails:grails-core:$grailsVersion"
_// required for Micronaut service generation, if present_ compileOnly "io.micronaut:micronaut-inject-groovy:$micronautVersion"
_// required for jackson ignores generation for Micronaut_ compileOnly 'com.fasterxml.jackson.core:jackson-databind:2.8.11.3'
}
This is the example of Grails plugin descriptor with zero-runtime dependencies on Grails:
@CompileStatic
class GormSharedDomainsGrailsPlugin {
String grailsVersion = '3.3.0 > \*'
String title = 'GORM Shared Domains Example'
String author = 'Vladimir Orany'
String authorEmail = 'vlad@agorapulse.com'
String description = 'Mimicking Grails Plugin'
}
The descriptor generator adds artefacts based on the location of the classes source code. So we need to create convenient source location known from Grails. Add the following lines to build into your Gradle file:
sourceSets { main { groovy { _// the source folder for the GORM domain classes_ srcDir 'grails-app/domain'
_// if you also want to include some services_ srcDir 'grails-app/services'
}
}
}
Now you have two more source directories which correspond the Grails layout. All the domain classes must be placed into grails-app/domain source folder. They still have to be annotated with grails.gorm.annotation.Entity.
import grails.gorm.annotation.Entity
@Entity
class Person {
String firstName
String lastName
String email
static _constraints_ = { firstName maxSize: 256
lastName maxSize: 256
email maxSize: 256, nullable: true
}
}
You should not use GORM static methods but rather utilise GORM Data Services. If you want to use the very same service from Micronaut then annotate it also with @Singleton. You need to place the class inside grails-app/services
import grails.gorm.transactions.Transactional
import groovy.transform.CompileStatic
import javax.inject.Singleton
@Transactional
@CompileStatic
@Singleton
class PersonService {
Person create(String firstName, String lastName, String email) {
Person person = new Person(
firstName: firstName,
lastName: lastName,
email: email
)
person.save(true)
return person
}
List<Person> getAllPersons() {
Person._list_()
}
}
You can check the generated build/classes/main/META-INF/ directory if it contains the generated descriptor:
Grails Usage
With this setup, you can reuse the domain classes and services from Grails without any additional configuration:
class PersonController {
PersonService personService
def index() {
if (!personService.allPersons) {
personService.create(
'Vladimir',
'Orany',
'vlad@agorapulse.com'
)
}
render personService.allPersons as JSON
}
}
Micronaut Usage
And from Micronaut as well:
@Controller(’/‘)
class PersonController {
private final PersonService personService
PersonController(PersonService personService) {
this.personService = personService
}
@Get('/')
List<Person> index() {
if (!personService.allPersons) {
personService.create(
'Vladimir',
'Orany',
'vlad@agorapulse.com'
)
}
return personService.allPersons
}
}
To enable domain classes for Micronaut you will probably have to change the packages scanned by Micronaut during the startup:
class Application {
static void main(String\[\] args) {
Micronaut._build_(args)
.packages('com.example')
.mainClass(Application)
.start()
}
}
Example Project
You can check the complete example in the following repository:
[musketyr/gorm-shared-domains