Tucker Pelletier
Posted on May 12, 2020
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.
Posted on May 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.