Layered Build and Packaging
Layers are a great way to manage builds, packages, versions, tests, deployment configurations, etc. Like Gradle, StrataCode lets you define build configuration using an object oriented, interpreted language. More concise, readable and flexible build configurations than XML. But StrataCode uses it's extended version of Java as the build configuration language so you do not have to learn Groovy and can leverage all of the built-in Java libraries you already know.
Just keep in mind, the code in the layer definition file runs at build/run time and so can't use the code in the layer itself. The layered, component framework you can use in build files reduces copying while improving customization potential for your builds. Layers make it easy to define simple modules - organized by file type or dependency rather than using a lot of project scaffolding. Modules are usually already organized by type so why not make them simpler? You'll end up with more modules that can be managed by more people.
Complete Control Over Your Code
StrataCode is more than just a build tool though. It's a complete code-processing environment with the ability to read and make incremental changes to your code. Unlike tools which manipulate your application's byte-code on-the-fly, you can debug the generated code and so can do more and still easily debug it when it breaks. You have all of the runtime safety benefits of compiled, statically typed Java code and avoid "untraceable code" side-effects created by dynamic languages, aspect-oriented programming, mixins, and other approaches.
Full IDE For Editing Build Files
StrataCode build files are written in StrataCode and are edited like normal files in IDE with code-hinting and edit-time validation.
Layer's can include maven libraries in one of two ways. Your projects can use maven itself for builds and/or installs of dependencies, then just configure the layer to use the resulting .jar in the classpath. A lighter weight approach uses StrataCode's built-in support for maven repositories and POM files, eliminating the need to install maven and make better use of layers for customization. One interesting advantage of layers, is the potential to switch back and forth between source and compiled forms of different aspects of your projects through a central point - i.e. putting a src layer in front of a compiled one, or to move source between one git repository and another in a more compatible way.
StrataCode Project File Organization
To start a new project with most systems, you use a "create project" wizard which generates the default project files from the configuration you specify. It's your job to figure out what files were generated, where the info you provided is now stored, and how to manage those configured values. Perhaps they are not the same from one developer to the other, or one deployment to the next. Maybe your framework stores these external properties in a separate file, or maybe not. If you're lucky there's some way to externalize them - via environment variables but it's always ad-hoc and varies from one framework to the next.
And the next time you upgrade your tool you may be required to manually update those files. Your intent for how you customized your project is buried. The way you want to organize your source files is defined by your framework, not by design of the developers for this particular type of project.
With StrataCode, when a layer extends a base-layer, it picks up all of the previous layer's project files, which includes the definitions of file formats and a default directory organization for this layer. You only need to specify files or settings you need to change, and you can change pretty much anything in the downstream layer, although it's best to stick with 'additive changes', that do not take away from the published contract for upstream layers. With framework features, you can layer your configuration - so each layer contains only the properties you want to set in that project, inheriting the rest. StrataCode combines the layers and generates one or more standard project directories. So your deployment configuration and tooling from that point on can be the same. This pattern both supports today's project structures, and let's you incrementally adopt a better way. One that reduces the project configuration, and improves the relevance of any configuration that's actually there.
StrataCode has a simple command-line integration with git for managing layers. Using layer definition files, you can easily change how a layer of code is defined - source of binary. You can move layers from one git repository to another, only by updating layer definition files. Because these files are statically typed and support IDE based tooling, everything is easier to manage.
Some branches in git can be avoided by creating layers which patch or replace code, then merged when they no longer are needed. You might copy some files or just add fields and override methods, create new types, etc. It can be a nice way to build a new feature because your changes are kept separate and together for easier navigation. You reduce conflicts with others changing different aspects of the same types - which would mean the same files if you did not use layers.
Layers and Contracts
Each layer can be thought of as supplying a set of API contracts - classes, properties, and types. They can be delivered in source or binary form, as long as these types are available to upstream code. When you can isolate how dependencies affect developers or other members of the team, you can optimize workflows - minimizing merge conflicts and making it easier to maintain compatibility over the lifetime of the project. Layers let you separate contracts from classes, processes, and git-repositories. They let you manage contracts between dependencies better. Mix and match, move and reassemble, plug and play. Some simple use cases:
- have one version of a layer retrieve a package as source, the other via a maven artifact - switch back and forth based on the need to debug the artifact, versus need for faster compiles
- layers which do not modify contracts act just like filters - options, you can turn on/off
- use layers to build code quickly, then refactor into shared, client and server layers - where the overlap defines the synchronization layer
Options for Module Packaging
In most project and build configurations, each module or project contains a lot of scaffolding - directories (and features) you might not need for any particular type of module: tests, configuration, source, documents, documentation, etc. When a project has lots of files of different types, it makes sense to separate them by type first. It makes things easier to find and reduces the size of your directories.
Other modules may already be organized by their type - tests, documentation, configuration, may be separated from the code according to important "lifecycles". When you combine files with different lifecycles in the same module you run into problems. Perhaps they are managed by different people, released at different times, included in different packages, organized in different source control systems, etc.
For these simpler modules, layers eliminate the scaffolding. Simply extend a layer which defines the context for your source files. It will make sure the files are deployed correctly - i.e. only run in test mode, includes in the test.jar file etc.
Layers support more flexible ways to organize files by their lifecycle, and their dependencies. In this case, you invert the directory structure - so you have test, doc, configuration, etc. groups of layers, each of which contains a simple directory of files. Instead of one module which is tightly coupled, you have the option to split off modules which are loosely coupled in a compatible way.
When you have simpler and more flexible module structures, you can create projects that are easier to manage, not just for developers but throughout the organization. Everyone contributes their "slices" of the feature set, where layers can become more feature oriented and less module or project oriented. Framework code controls how those assets are deployed. Static typing and tools keep things structured in the same way a complex object-oriented application is structured. That helps detect conflicts and gives tools for fixing them. This lets developers more flexibly organize source code amongst different git repositories and move back and forth maintaining compatibility with upstream layers.
Anyone who has worked in a big Java application is aware of how difficult it can be to manage dependencies. You have to consider not only your dependencies but the "transitive dependencies" - dependencies of your dependencies. How do you ensure compatible versions of the dependencies but also pick up the latest security fixes when they are available? How do you understand what's happening and resolve conflicts - to select the right version, even in the common case when you are inheriting conflicting rules for overlapping packages? When a critical security patch comes out, who do you trust to delegate your transitive dependencies so you pick up that patch as soon as necessary?
Maven provides a flexible but intricate system to specify, inherit, exclude and resolve conflicts between the versions you inherit from transitive dependencies. The order in which modules are sorted is determined by the order in which the dependency graph is traversed. That's based on the order of dependencies in each pom.xml file, and the number of levels of indirection before you reach a given module. So dependencies inherited that are further away are overridden by those that are closer to the first build component.
Layers work in a similar way, but simplify the specification of dependencies, and allow a more intuitive way to override them. With layers, it's the stacking order that determines precedence in all things. That order is based on dependencies primarily but there are more ways to control the stacking order. Layers are organized into framework, application, and configuration stacks which are independently sorted. The order of the layers, and dependencies can be traced using the IDE, and quickly overridden in a way that you can carefully ensure that override only shows up in the right situations.
It's also easy to separate the transitive dependencies into a new layer - so you can include a package, but not include it's dependencies. Or switch transitive dependencies by switching a layer. That's a better alternative than excluding dependencies, since who knows when those will actually be required rules on your dependencies. This way, a required dependency is always included but these packages of dependencies can be included optionally. Dependencies can be represented as separate layers, features by themselves by with a simple layer definition file that has no code.
Most types of software projects require some way to tag builds with a version number, build date, commit hash, rc #, etc. To achieve the most diagnostic accuracy, developers prefer to tag the executable itself. If you store the version number in a separate file, it's not guaranteed to match what's actually running.
StrataCode offers the BuildInit annotation to provide a simple, declarative way to turn an expression that runs at build time into the initialization value for a property at run time. See the BuildTag example.
Read more and see examples in the documentation.