Releases: go-goyave/goyave
Release v5.0.1
- httputil: fixed a race condition when initializing the quality value regex
- util/session: improved support for nested transactions
Release v5.0.0
Introduction
Goyave v5 has been in the oven for over two years. With the first production applications deployed, a ton of issues and flaws revealed themselves, preventing real long-lived projects from cleanly evolving. The initial goals of the framework started to weaken the more the applications were developed and changed: the strong basis it promised to provide wasn't actually that strong. From this invaluable experience, I decided to go back to the drawing board and redesign the framework.
This new major version is an almost entire rewrite of the framework. In order to limit the frequency of releases containing breaking changes, all accumulated ideas for reworks and improvements that would introduce those were grouped in v5. This new major version not only aims at fixing the outstanding issues and design flaws, but also improves on existing features by upgrading them to the latest available technology. Expect v5 to feel modern and to neatly integrate with all the new language features such as generics, file systems, structured logging, and more.
These release notes will be organized in categories. They will explain the overall change of direction for each area of the framework as well as shortly showcase the new or improved features. The list of changes may be incomplete as many features were rewritten entirely.
Motivations
Among the many aspects that needed to be reworked, some of them stood out and fueled the initial drive to rewrite the framework.
Dependency coupling
Every layer and components of a v4 application had strong dependency coupling. The HTTP, business and database layers were all mixed up together. The framework was systematically imposing a dependency to itself, direct or indirect. Its locked-up architecture, entirely based on globals was hindering the more business-oriented applications. Those struggled to detach their domains from the rest of the application, and encountered obstacles every time they needed to handle more complex business logic.
For example: to access the database, you were forced to use the framework, which was loaded from a configuration system that also was handled by the framework. This created a long chain of dependencies that was hard to separate from the rest, even more so when it came to writing tests.
On top of that, the all-global architecture required a ton of synchronization, which were detrimental to the overall performance of the application.
Locked architecture
All components of the framework were strongly linked together and quite opaque, despite an initial effort made to make the framework flexible and hackable. In the end, many non-elegant workarounds had to be made in real-world scenarios. This made it harder to adapt an application to the constraints often encountered by companies developing their ecosystem and trying to solve real-world issues that needed deeper access to the inner-workings.
The validation system was one of the biggest, if not the biggest, culprit. It was very inconvenient to use compared to the new one brought with v5. The hacks required to make some advanced field comparison or business-logic validation were very fragile and hard to maintain. This design made it impossible to re-use code, and forced the hassle of creating a new validator for every single specific use-case.
The framework was also a bit too reliant on magic in some aspects. Many functions were using weak typing (any
) and reflection, or even string identifiers, all for the sake of conciseness. But this came at a cost: no compile-time checks, hard code navigation, no completion, the need to rely on documentation all the time, etc. In the end, by trying to be concise for a better DX (developer experience), the framework sacrificed code cleanliness, reliability, readability, maintainability and actually ruined its DX this way.
Testing
All these issues accumulate and become a huge pain the moment you start trying to add tests to your project. They were very difficult to write, which is the exact opposite of what you want. Tests should be painless and as easy to read and maintain as possible. Also suffering from the locked architecture and mandatory dependency to the framework, they couldn't even be run in parallel. Almost nothing could be mocked because of the dependency coupling. This was in turn forcing you to use the database for your tests, which made tests even slower and complicated.
In short, the design of the framework prior to v5 treated tests as an after-thought despite how important they are.
Streamlining
There were many smaller and non-blocking issues as well. Together they made the entire development flow awkward by moments, like there was a missing piece to make the whole process fluid from start to finish.
The first one was the relation between the user-sent data and the internal data. Making use of the data sent by users was inconvenient and also quite unsafe, requiring a ton of type assertions and map navigation. This is very subpar compared to most Go applications which use structures. It also caused problems when interacting with the database.
Another issue was error handling and debugging. The framework was relying too much on panic
, which is not a very sane nor idiomatic way to handle errors. Although it allowed for a pretty good debugging experience for developers with more precise stacktraces, a much better solution was possible. This solution wouldn't compromise on code quality for the sake of DX, again.
The last major one was the low interoperability and control, notably with the lack of access over the context
API, or some missing configuration options and settings.
Philosophy
The overall philosophy of the framework stays the same. Goyave remains an opinionated framework, focused on DX (developer experience) by enabling quick and enjoyable development by being expressive, reliable and complete. The goal is to make business logic development as painless as possible by handling as many recurring things for developers as possible so they can focus on implementing what actually creates value for their company.
New direction
However, there are some important evolutions in the general direction of the framework:
- v5 takes architecture and utilities one step further. At first glance, a typical application will be less simple to grasp. Therefore, the "progessive" nature of the framework is no more.
- Despite its opinionated nature, the new design tries to be as open as possible. The framework should not be a black box and developers should have no trouble making it their own effortlessly.
- The design should focus its efforts on creating tools that simplify the reduction or elimination of the dependencies between application layers and business layers.
- The design and development of the framework now assumes full focus on catering to businesses and medium to large projects.
- The development of the framework will now consider real-world use-cases and scenarios with more importance. No "quick and easy" solutions, tools or fancy gadgets will be developed from now on.
- The framework and documentation will expand even more on architecture recommendations and good practices to cover more questions developers could have when working on their project. The goal of an opinionated framework is to save time to its users, while giving them all the tools they need to do produce the best possible quality software.
- The open-source process has been revised to make contributions easier. Gathering and taking care of a strong community has always been very important. More efforts will be made in this direction.
Architecture
The framework goes even further than before when it comes to architecture recommendations for your projects. The goal is to answer more of the questions any team will inevitably encounter when setting up their project. By providing tools that will works seemlessly with this architecture, the frameworks aims at saving you a lot of time and make the development process as fluid as possible.
A project build with Goyave v5 will be split in three distinct layers:
- Presentation: HTTP/REST layer, it's your application's facade
- Domain/Business: contains services
- Data: interacts with the database with repositories and contains the models
Each layer doesn't directly depend on the others because they define interfaces representing their own needs. The following chart describes the usual flow of a request into a Goyave application.
This architecture has several advantages:
- Good separation of concerns and no direct dependency
- Easily testable
- The data layer doesn't leak into the business layer even if there are transactions involved
- Lowers the risk of exposing information that is not meant to be public
- Easily readable, explorable and maintainable
In Goyave v5, nothing is global anymore. The costly need for goroutine synchronization is eliminated. The overall design is now interface-focused. On top of that, the entire framework now takes full advantage of the standard context
API and encourages it use.
Components
Because nothing is global, a mechanism is necessary so the server's essential resources (such as the configuration, logger, etc) can be distributed to every component of the server. This mechanism is actually called Components, and described by the interface goyave.Composable
. Most structures in the presentation layer actually are Goyave components: controllers, middleware, validators, etc.
Server
Changing all global resources to non-global required a central element that would hold them all. A server contains all the base resourc...
Pre-release v5.0.0-rc14
- Fixed a panic occurring in validation when the expected type is a slice but the value is not.
Pre-release v5.0.0-rc13
- The CORS middleware is now global.
- Updated docs.
Pre-release v5.0.0-rc12
- Fixed "invalid db" error when using
server.CloseDB()
on a server having a DB with a custom dialector (usually for tests). This error is now ignored. osfs
: added a constructor with an optional base directory parameter:osfs.New("/home")
.- Validation: fixed a reflect error when trying to validate elements of a
nil
array.
Pre-release v5.0.0-rc11
- Updated some documentation
- Use
errors.Is()
instead of strict equality forhttp.ErrServerClosed
- Database initialization now return previously omitted errors
fsutil.File
now supports DTO conversionosfs.FS
new methodSub()
returns a sub OS file system- Use
path.Join()
instead of concatenation testutil.FindRootDirectory()
doesn't add a trailing slash anymore and cleans up the path. Use withpath.Join()
instead of concatenation.
Pre-release v5.0.0-rc10
- Cleanup
- Removed unused structure in the auth package
- Updated some documentation in the
fsutil
package
- Reworked the
auth
package- Authenticators depend on a
UserService
instead of executing DB query themselves - Encourage using user DTO instead of models
- This all results in removing a dependency between presentation and data layer
- Removed
FindColumns
. The "username" and "password" fields are not marked with struct tags anymore: they are handled by a repository. - Reduced (but not eliminated) uses of reflection. Now only the password field from the DTO returned by the user service is retrieved using reflection.
- Take advantage of generics even more
- The request's context is now properly propagated by JWT and basic authenticators
- Authenticators depend on a
Pre-release v5.0.0-rc9
- Slog: the dev mode handler now uses a pointer for its mutex and shares the same mutex among all his clones.
Server.Stop()
won't attempt to closesigChannel
again if the server has already been stopped. This method can this be called several times safely now.- Router
- Changed the subrouter matching so it doesn't work with prefix only. This will remove conflicts between two subrouters having a prefix starting with the same characters (e.g.:
/test
and/test-2
won't conflict anymore) - The router won't turn back and explore other branches if the subrouter matches but none of its routes do. This will fix some false matches.
- Fixed
/
route defined at the main router being matched if a subrouter matches but none of its routes do and a trailing slash is present in the request URI.
- Changed the subrouter matching so it doesn't work with prefix only. This will remove conflicts between two subrouters having a prefix starting with the same characters (e.g.:
- Cleanup: removed some unused resources, outdated comments and updated incorrect documentation
- Validation
- Fixed a reflect error in
ArrayValidator
if the first element of the array is invalid (e.g.:nil
) - Use error wrapping in
Unique
/Exists
validators for better error stacktraces - Add a timeout on the SQL query executed by the
ExistsArray
validator. - Remove suffix
[]
from n-dimensional array elements field name in error messages.
- Fixed a reflect error in
Pre-release v5.0.0-rc8
- Improved docs
server.Stop()
doesn't attempt to stop the server a second time if it was already stopped- Validation: added
validation.Context
accessors for current element path and array element errors - Validation: added
validation.Context
methodsAddValidationError
andAddValidationErrors
so validators can merge additional errors that may be coming from nested validation validation.Context
'sExtra
field is nevernil
- Fixed
OPTIONS
method not added to routes if their router inherited their CORS settings from parent router - Updated dependencies
- Improved language files unmarshal error messages
Pre-release v5.0.0-rc7
- Updated dependencies
- Improved thread-safety of server status
- Updated contribution resources (issue templates, etc) and license