Grails 2.5.5 to Grails 3.3.10 Postmortem
Tags: grails, groovy
Published: 2020-05-12 01:44:43

Here's my postmortem of what I did in an upgrade from Grails 2.5.5 to Grails 3.3.10 I figured this may help someone... The app I was working on was fair-sized about 80,000 lines of Groogy/Grails and 40,000 lines of GSP, not including angular code, which is where the app is moving to. How hard a Grails 2 to 3 upgrade is a function of:
How many bad practices your codebase had * the number of lines of code.

Tasks/commits:
* Moved a lot of db writes in controllers to services with transactions
* Using the resource plugin had to upgrade to the Asset Pipeline
* Layouts, being used as templates
* Inline plugins that were deprecated, needed to be detangled and removed
* Moving a lot of code from web-app to src/main/webapp and fixing hardcoded links in js files
* Repackaged controllers, services, and domain objects from one or no package, into something more manageable.
* Porting JQgrid for older GSP code because the plugin wasn't ported to Grails 3
*Reworking the special password encoder that uses a hard-coded salt, one from the db, and one passed in from config. Later ditch for Bcrypt
* Porting a custom constraint to Grails 3
* Porting log4j to logback
* Refactoring a Util class that used GrailsDomainClass API to the Persistent API, and several classes that used it, Did changes several times, Was Like a game of wack a mole.
* Replacing ../  with / in gsp files
* Replacing a deprecated MD5Codec with encodeAsMD5
Ported AsyncEventPublisher to use Grails events
* Replacing CommonsMultiPartFile with CommonsMultiPartFile
* Fixing some static compilation issues
* Converting Filters to Interceptors
* Two versions on Angular
* Old GSPs using Jquery instead of Angular
* Fixing a class that was using an @Override that wasn't overriding anything...
* Fixing a bunch of custom validators that broke on upgrade
* Removing a lot of @Slf4j annotations that were unnecessary, and caused issues with some traits
* Fixed imports app, unit tests, and integration tests.
* Grails Mocks needed to be replaced
* GrailsUnitTestCase needed to be replaced.
* Validateable annotation had to be replaced with the Validateable trait
* Removing unused dependency that was causing build errors.
* Fixing a bunch of queries that were using string interpolation, but were also parameterized
* Fixed Spring security Roles missing the ROLE_ prefix, several times, all over the app
* Rolled back some of the Role prefix changes, because the role table was used for security roles, and roles in a different sense.
* Ported integration tests to use @Integration and @Rollback
* Added the external config plugin, to maintain the external configuration. And convert the YML, to Groovy config.
* JWT didn't have a secret key set in Grails 2 but needed it in Grails3
* Added @Transactional in some places that were missing it...
* Fixed integration tests, that were doing setup calls outside the setup methods
* Making integration helper classes that generate data in setup methods @Transactional
* Adding missing @Mock([
* Replacing constraints with constrainedProperties
* Eliminating with{} in Spock tests
* Removed autowire from Domain classes
* Added pathingJar = true for windows people
* Dealt with various Gradle commands not having access to System properties, which are needed for licensing validation.
* Fought with a test that was failing because of @Memoized
Sank a lot of time trying to get the test reports in the right place for the Jenkins jobs to pick them up
* A method that was annotated with @Transactional(readOnly=true) was doing a delete
* Fixed bouncy castle Gradle excludes
* Replaced PEMReader with PEMParser
* Fixed the way we got metadata for the app version
* Replaced uses of jdbcTemplacte to fix some integration tests
* Fix some HQL queries that were using id instead of the domain object
* Replaced some uses of a custom ApplicationContextHolder with Holders, which solved some issues
* Removed some deprecated logic dealing with releaseLocalThreadMemory() DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP.get().clear()
* Added Try catch to the interceptor and fixed the issue so the logs don't blow up.
* Moved a cascade from constraints to mapping
* Found several times that params don't have to be included on redirects and forwards, and if they are they can be duplicated into an array
* Fixed a GSP that could include properties or not... wrapped with a <g:if tag
* Updated Quartz jobs to work with new plugin swapping static with def
* Fixed command objects trait to ignore certain properties like constraintsMap, metaClass, class, constraints
* Fixed a test that was missing a controller context...
* Fixed several issues where associated properties of a domain class were not accessible in GSP files, so passed then as part of the model
* Started to have memory issues with the DB migration, This was cleared up by removing a lot of legacy changes from the changelog, and rebasing to a new "gold" image to start.
* All DB migration scripts in one folder(updated in the middle of a sprint right before a release) to be by version.
* Fixed a bunch of URLs, that didn't have the application context
*Command Object not binding body of delete, because it's not supposed to have one, used a util method that does the binding for me.
* Several places used the query parameter format, that didn't work the way it did before. used withFormat in several cases, some that I shouldn't have
*Refactored the places that I shouldn't have used withFormat to use command objects, because the params could come in as ajax post, or form post.
* Found a melody plugin issue between @Transactional and a Property of type class
* Swapped from failOnError:false to failOnError:true, and making a lot of code changes because of it. Don't do this, this is wrong.
* Fixed imports in GSP files



Things that held up or slowed down progress, things hopefully you can avoid:

* Had to continue to merge changes coming from the Grails 2 branch that was actively being developed, and fix things as they broke.
* Moved new tests/integration test/web-app/src/groovy files to there new locations after merges
* Still had to work on pull request periodically
* Two versions on Angular, which was later reduced to one
* Got pulled back to work on the previous version of the app is several cases
* About 60+ security issues(XSS, SQL injection, CSRF) which took over the team for quite a while
* After which we stopped merging, between branches and had to cherry-pick between the three branches were are maintaining(two Grails 2, and the new Grails 3)
* Was left without QA for over a month.

More resources:
* https://erichelgeson.github.io/blog/2016/04/17/upgrading-grails-3/
* https://docs.grails.org/3.0.x/guide/upgrading.html
* https://app.slack.com/client/T07LCCCSU/C07M0GTDE/thread/C07LCNWHG-1588321026.141200
* https://www.danvega.dev/blog/2017/06/16/migrating-grails-2-x-applications-grails-3-x/
* http://philip.yurchuk.com/software/upgrading-to-grails-3/
* https://opensource.com/article/18/5/upgrading-grails-applications
* https://guides.grails.org/grails-quickcasts-developing-grails-3-applications-with-intellij-idea/guide/index.html

This wasn't my first Grails 2 to 3 upgrade, I did another one that wasn't anywhere near as complicated, but the codebase was under 10,000 line of Groovy code. I'll publish another article for my Grails 4 postmortem, which is much shorter.