Architecture

Fitness functions

Once we build a product we must ensure its intrinsics stay healthy. In other words fit. We need to build some constraints that we must validate against every time we apply changes to our system. If those constraints fail validation the system should protect itself and not permit those changes. Every architecture should have a few of these.

https://disher.com/2017/02/17/product-constraints-can-catalyst-great-design/

Sonar

The most obvious is static analysis tools like Sonar. It has tons of constraints that are applicable at code level. All these constraints are bundled into something called quality gate. The default sonar quality gate is a good place to start.

You can add more constraints as you go. I would recommend adding

Cognitive and cyclomatic complexity ideally should be at most 10 as per the accepted ranges.

  • 1 – 10 = Minimal Risk
  • 11 – 20 = Moderate Risk
  • 21 – 50 = High Risk
  • > 50 = Very High Risk

But let’s face it anything with a moderate risk is acceptable. A note on this community sonar will not calculate it per class but per project which is not ideal, so it will add all the complexities of the classes and validate it against that number.

Also we don’t want blocker issues and any skipped unit tests. This analysis should be part of the project build stage.

OWASP check

We must ensure that 3rd-party libraries that we use have no critical security vulnerabilities. In order to ensure this we check the dictionary of common vulnerabilities which is handled by the OWASP community. In order to achieve this I suggest using a maven plugin

   <profile>
      <id>fitness</id>
      <build>
        <plugin>
          <groupId>org.owasp</groupId>
          <artifactId>dependency-check-maven</artifactId>
          <version>${dependency-check-maven.version}</version>
          <configuration>
            <failBuildOnCVSS>8</failBuildOnCVSS>
            <failOnError>false</failOnError>
          </configuration>
          <executions>
            <execution>
              <phase>test</phase>
              <goals>
                <goal>check</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </build>
      <properties>
        <groups>fitness</groups>
      </properties>
    </profile>

and wrap it with a maven profile so that you can control when to run it. Also should be part of the build process. This will generate a report that contains information about the found vulnerabilities.

Here we see that the CVSS score is 2.1 which is ok. Ideally there should be no vulnerabilities but a threshold of CVSS=8 is enough. If there is vulnerability with a greater score it will fail the build.

Library license check

Obviously we don’t want to share our source code due to some license agreement that we agreed to without reading it. Here is a list with all the software licenses. Obviously the Disclose Source column is important.

This mean that we want to blacklist all with this constraint. For this the plugin it’s available at https://github.com/mrice/license-check.

 <plugin>
        <groupId>org.complykit</groupId>
        <artifactId>license-check-maven-plugin</artifactId>
        <version>0.5.3</version>
        <configuration>
          <blacklist>
            <param>agpl-3.0</param>
            <param>gpl-2.0</param>
            <param>gpl-3.0</param>
          </blacklist>
          <excludes>
            <param>org.assertj:assertj-core:3.12.2</param>
          </excludes>
        </configuration>
        <executions>
          <execution>
            <phase>verify</phase>
            <goals>
              <goal>os-check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

This will check your repo against the open source library. In practice you will need to add some excludes because some of your libraries have no license(in case you import libraries from your own repo) and also there are some 3rd party libraries like assertj-core which do not provide a license.

If this constraint fail you build will fail with

Failed to execute goal org.complykit:license-check-maven-plugin:0.5.3:os-check (default) on project my-project: blacklist of unverifiable license

Layering contraints

Given the clean architecture concept

the rule that must be respected here is that each outer layer is aware only of the next inner layer. In this example the blue circle can only know about the green one and if it tries to access the red one that would mean a break in the rules. We want to protect this. Also entities and use cases should be isolated from any infrastructure. This we need to guard also. For this we can use archunit. It’s simple as creating a unit test which will run for each build. It should be separated from the regular unit tests.

 @AnalyzeClasses(packages = "com.sergiuoltean.blog")
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = classes().that().resideInAPackage("com.sergiuoltean.domain")
          .should().accessClassesThat()
          .resideInAnyPackage("com.sergiuoltean.domain", "java..");

}

There is also a maven plugin for apply rules in a less verbose way.

<plugin>
	<groupId>com.societegenerale.commons</groupId>
	<artifactId>arch-unit-maven-plugin</artifactId>
	<version>2.4.0</version>
	<configuration>
		<projectPath>${project.basedir}/target</projectPath>
		<rules>
			<preConfiguredRules>
				<rule>com.sergiuoltean.LayeringRuleTest</rule>				
			</preConfiguredRules>
		</rules>
	</configuration>
	<executions>
		<execution>
			<phase>test</phase>
			<goals>
				<goal>arch-test</goal>
			</goals>
		</execution>
	</executions>
    <dependencies>
       <dependency>
            <groupId>com.societegenerale.commons</groupId>
            <artifactId>arch-unit-build-plugin-core</artifactId>
            <version>SOME_VERSION_GREATER_THAN_2.3.0</version>
       </dependency>
    </dependencies>
</plugin>

An example of rule is

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.core.importer.Location;

public class ExclusionImportOption implements ImportOption {

    String patternToExclude;

    public ExclusionImportOption(String patternToExclude) {
        this.patternToExclude=patternToExclude;
    }

    @Override
    public boolean includes(Location location) {

        if(location.contains(patternToExclude)){
            return false;
        }
        return true;
    }
}

This opens the world for different constraints.

Annotation check constraints

One of the check that I recommend here is to make sure every service(annotated with @Service) that uses a repository to be set to be transactional(@Transactional). An example can of how can we achieve this

@AnalyzeClasses(packages = "com.sergiuoltean.blog")
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = classes().that().areInterfaces().and().areAnnotatedWith(Repository.class)
          .should().onlyBeAccessed().byClassesThat().areAnnotatedWith(Transactional.class);

}

Another use can would be when you have a custom annotation (eg authorization check) and you want to make sure that the methods in the @RestController are guarded by this check. If there’s a violation you should be aware of it.

@AnalyzeClasses(packages = "com.sergiuoltean.blog")
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = methods().that().areDeclaredInClassesThat().areAnnotatedWith(RestController.class).should().beAnnotatedWith(Authorized.class);

}   

There’s also a more subtle use case. It’s important that each transactional method to be public. Of course if you go with the annotation on the method not only on the class.

@AnalyzeClasses(packages = "com.sergiuoltean.blog")
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = methods().that().areAnnotatedWith(Transactional.class).and()
          .areDeclaredInClassesThat()
          .areAnnotatedWith(Transactional.class).should().haveModifier(JavaModifier.PUBLIC);

}
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'methods that are annotated with @Transactional should have modifier PUBLIC' was violated (1 times):

You want to make sure your rest methods are documented with Swagger? Piece of cake.

@AnalyzeClasses(packages = "com.winnowsolutions.hub.coverssales.rest")
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = methods().that().areDeclaredInClassesThat()
          .areAnnotatedWith(RestController.class)
          .should().beAnnotatedWith(ApiOperation.class);
}
java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'methods that are declared in classes that are annotated with @RestController should be annotated with @ApiOperation' was violated (1 times):

Cycle check constraints

With archunit we can easily test for cycles. Our packages should not contain any cycles

@AnalyzeClasses
public class MyArchitectureTest {

  @ArchTest
  public static final ArchRule myRule = slices().matching("com.sergiuoltean.(*)..").should().beFreeOfCycles();

}

Archunit also offers a way to build up your rules.

Clean code constraints

In the book Clean Architecture by Uncle Bob there is this plot

which describes the state of a system at any point in time. As you guessed our goal is to be on the main sequence. Considering these definitions

  • CC – Concrete Class Count
  • AC – Abstract Class (and Interface) Count
  • Ca – Afferent Couplings (Ca), outgoing dependencies
  • Ce – Efferent Couplings (Ce), incoming dependencies
  • A – Abstractness (0-1)
  • I – Instability (0-1)
  • D – Distance from the Main Sequence (0-1)

A = AC / (AC+CC)

S = Ca / (Ca+Ce)

I = 1 / S

D = A + I – 1

Feels like back to math class, isn’t it?

Zone of pain is where our code is highly volatile and concrete, which means that any change is hard to implement and we don’t know the impact it may have on other parts of the code.

Zone of uselessness is where our code is abstract but nobody needs those abstractions.

With a simple unit test we can determine how far are we from the ideal line.

  @Test
  @DisplayName("Should not deviate from main sequence more than tolerance")
  protected void shouldNotDeviateFromMainSequence() throws IOException {

    jdepend = new JDepend();

    jdepend.addDirectory("path/to/target/classes");
    double ideal = 0.0;
    double tolerance = 0.5;

    SoftAssertions softly = new SoftAssertions();

    jdepend.analyze().forEach(p -> {
      JavaPackage pck = ((JavaPackage) p);
      softly.assertThat(pck.distance()).as(pck.getName()).isLessThan((float) (ideal + tolerance));
    });
    softly.assertAll();

  }

The problem with this function is that the numbers are just guidelines. The decision to put this fitness function in place should be weigh in carefully. If the decision is to not use it as a function you can still get some benefit by integrating the jdepend maven plugin to generate a fitness report to get a snapshot of the system state at build time. There are also some metrics is sonarqube.

Change Risk Analysis and Predictions (CRAP)

There is this Crap4J tool which uses a combination of cyclomatic complexity and code coverage and will tell you how CRAPy is your code.

CRAP(m) = cc(m)^2 * (1 – cvg(m)/100)^3 + cc(m)

cc = cyclomatic complexity

cvg = code coverage

Here the threshold is set to 30 by default, but this should be lowered. Anything about 50 means we have a problem. No amounts of tests will rescue the code from crappiness. Know your CRAP(measure it) and cut the CRAP(reduce it).

Monitoring functions

These are a different types of fitness functions. An example would be cost control techniques in your cloud provider such as aws. Another example can be usage of new relic and setup some kinds of alerts.

Other functions

These can include security integration tests, using security static analysis tools, performance metrics, chaos engineering, etc.

Conclusion

These are just guidelines and may or may not work for your situation. These should not be regarded as drivers is some kind of metric driven development but as what they are which is a way of ensuring the system you built is still fit. A balance must be kept. Some of them are architectural functions, some ensure the proper use of libraries, the idea is that we need some of these to not diverge too much from the design. It’s an excellent tool that should be in the pocket of every solution architect and it allows him to leave projects in a good shape so that he can focus on multiple projects without being worried that somebody might destroy what he had built.

1 thought on “Fitness functions”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.