Skip to content

Commit

Permalink
Impact analysis #79
Browse files Browse the repository at this point in the history
  • Loading branch information
tillias committed Nov 1, 2020
1 parent a20655b commit 1e21ec1
Show file tree
Hide file tree
Showing 15 changed files with 628 additions and 105 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<properties>
<!-- Build properties -->
<maven.version>3.3.9</maven.version>
<java.version>1.8</java.version>
<java.version>10</java.version>
<node.version>v12.16.1</node.version>
<npm.version>6.14.5</npm.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import com.github.microcatalog.domain.custom.ReleaseGroup;
import com.github.microcatalog.domain.custom.ReleasePath;
import com.github.microcatalog.domain.custom.ReleaseStep;
import com.github.microcatalog.domain.custom.impact.analysis.Group;
import com.github.microcatalog.domain.custom.impact.analysis.Item;
import com.github.microcatalog.domain.custom.impact.analysis.Result;
import org.ehcache.config.builders.*;
import org.ehcache.jsr107.Eh107Configuration;

Expand Down Expand Up @@ -60,6 +63,11 @@ public JCacheManagerCustomizer cacheManagerCustomizer() {
createCache(cm, ReleaseGroup.class.getName() + ".steps");
createCache(cm, ReleasePath.class.getName());
createCache(cm, ReleasePath.class.getName() + ".groups");
createCache(cm, Item.class.getName());
createCache(cm, Group.class.getName());
createCache(cm, Group.class.getName() + ".items");
createCache(cm, Result.class.getName());
createCache(cm, Result.class.getName() + ".groups");
// jhipster-needle-ehcache-add-entry
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.hibernate.annotations.CacheConcurrencyStrategy;

import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

/**
Expand Down Expand Up @@ -35,4 +37,10 @@ public ReleaseGroup removeSteps(ReleaseStep releaseStep) {
public void setSteps(Set<ReleaseStep> releaseSteps) {
this.steps = releaseSteps;
}

public Optional<ReleaseStep> findByTargetId(final long targetId) {
return steps.stream()
.filter(i -> Objects.equals(targetId, i.getWorkItem().getId()))
.findAny();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.github.microcatalog.domain.custom.impact.analysis;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Group {
private Set<Item> items = new HashSet<>();

public Set<Item> getItems() {
return items;
}

public Group items(Set<Item> items) {
this.items = items;
return this;
}

public void addItem(final Item item) {
if (item == null) {
return;
}

this.items.add(item);
}

public void setItems(Set<Item> items) {
this.items = items;
}

public Optional<Item> findByTargetId(final long targetId) {
return items.stream()
.filter(i -> Objects.equals(targetId, i.getTarget().getId()))
.findAny();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.github.microcatalog.domain.custom.impact.analysis;

import com.github.microcatalog.domain.Microservice;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import java.util.List;

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Item {
private Microservice target;
private List<Microservice> siblings;

public Microservice getTarget() {
return target;
}

public Item target(Microservice microservice) {
this.target = microservice;
return this;
}

public void setTarget(Microservice microservice) {
this.target = microservice;
}

public List<Microservice> getSiblings() {
return siblings;
}

public Item siblings(List<Microservice> siblings) {
this.siblings = siblings;
return this;
}

public void setSiblings(List<Microservice> siblings) {
this.siblings = siblings;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.github.microcatalog.domain.custom.impact.analysis;

import com.github.microcatalog.domain.Microservice;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Result {
private Instant createdOn;
private List<Group> groups = new ArrayList<>();
private Microservice target;

public Instant getCreatedOn() {
return createdOn;
}

public Result createdOn(Instant createdOn) {
this.createdOn = createdOn;
return this;
}

public void setCreatedOn(Instant createdOn) {
this.createdOn = createdOn;
}

public List<Group> getGroups() {
return groups;
}

public Result groups(List<Group> releaseGroups) {
this.groups = releaseGroups;
return this;
}

public void addGroup(final Group group) {
if (group == null) {
return;
}

this.groups.add(group);
}

public void setGroups(List<Group> releaseGroups) {
this.groups = releaseGroups;
}

public Microservice getTarget() {
return target;
}

public Result target(Microservice microservice) {
this.target = microservice;
return this;
}

public void setTarget(Microservice microservice) {
this.target = microservice;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.github.microcatalog.service.custom;

import com.github.microcatalog.domain.Microservice;
import com.github.microcatalog.service.custom.exceptions.MicroserviceNotFoundException;
import org.jgrapht.Graph;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.alg.cycle.CycleDetector;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.DefaultEdge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;

public abstract class GraphOperationsService {

private final Logger log = LoggerFactory.getLogger(GraphOperationsService.class);

protected final GraphLoaderService graphLoaderService;

protected GraphOperationsService(GraphLoaderService graphLoaderService) {
this.graphLoaderService = graphLoaderService;
}

protected GraphContext getConnectedSubgraphWithoutCycles(final long microserviceId) {
final Graph<Microservice, DefaultEdge> graph = graphLoaderService.loadGraph();

if (graph.vertexSet().isEmpty()) {
return new GraphContext(graph, null);
}

final Optional<Microservice> maybeTarget = graph.vertexSet()
.stream().filter(v -> Objects.equals(v.getId(), microserviceId)).findFirst();

// can't build release path, cause microservice with given id is not present in graph
if (!maybeTarget.isPresent()) {
throw new MicroserviceNotFoundException("Microservice not found", microserviceId);
}

final Microservice target = maybeTarget.get();

final ConnectivityInspector<Microservice, DefaultEdge> inspector = new ConnectivityInspector<>(graph);
final Set<Microservice> connectedSet = inspector.connectedSetOf(target);

// Connected subgraph, that contains target microservice
final AsSubgraph<Microservice, DefaultEdge> targetSubgraph = new AsSubgraph<>(graph, connectedSet);
log.debug("Connected subgraph, that contains target microservice: {}", targetSubgraph);

final CycleDetector<Microservice, DefaultEdge> cycleDetector = new CycleDetector<>(targetSubgraph);
if (cycleDetector.detectCycles()) {
final Set<Microservice> cycles = cycleDetector.findCycles();
throw new IllegalArgumentException(String.format("There are cyclic dependencies between microservices : %s", cycles));
}

return new GraphContext(targetSubgraph, target);
}

protected static class GraphContext {
private final Graph<Microservice, DefaultEdge> graph;
private final Microservice target;

public GraphContext(Graph<Microservice, DefaultEdge> graph, Microservice target) {
this.graph = graph;
this.target = target;
}

public boolean hasEmptyGraph() {
if (this.graph == null) {
return true;
}

return graph.vertexSet().isEmpty();
}

public Graph<Microservice, DefaultEdge> getGraph() {
return graph;
}

public Microservice getTarget() {
return target;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.github.microcatalog.service.custom;

import com.github.microcatalog.domain.Microservice;
import com.github.microcatalog.domain.custom.impact.analysis.Group;
import com.github.microcatalog.domain.custom.impact.analysis.Item;
import com.github.microcatalog.domain.custom.impact.analysis.Result;
import org.jgrapht.Graph;
import org.jgrapht.graph.AsSubgraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.EdgeReversedGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.jgrapht.traverse.GraphIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Transactional
public class ImpactAnalysisService extends GraphOperationsService {

private final Logger log = LoggerFactory.getLogger(ImpactAnalysisService.class);

public ImpactAnalysisService(GraphLoaderService graphLoaderService) {
super(graphLoaderService);
}

public Optional<Result> calculate(final Long microserviceId) {
final GraphContext context = getConnectedSubgraphWithoutCycles(microserviceId);
if (context.hasEmptyGraph()){
return Optional.empty();
}

final Graph<Microservice, DefaultEdge> reversed = new EdgeReversedGraph<>(context.getGraph());

// Calculate all vertices, you can reach from target
final Set<Microservice> affectedMicroservices = new HashSet<>();
GraphIterator<Microservice, DefaultEdge> iterator = new DepthFirstIterator<>(reversed, context.getTarget());
while (iterator.hasNext()) {
affectedMicroservices.add(iterator.next());
}

final Graph<Microservice, DefaultEdge> affectedGraph = new AsSubgraph<>(reversed, affectedMicroservices);

final Result result = new Result().target(context.getTarget());

do {
final List<Microservice> verticesWithoutIncomingEdges = affectedGraph.vertexSet().stream()
.filter(v -> affectedGraph.incomingEdgesOf(v).isEmpty())
.collect(Collectors.toList());
log.debug("Leaves: {}", verticesWithoutIncomingEdges);

final Group group = createGroup(affectedGraph, verticesWithoutIncomingEdges);
result.addGroup(group);

verticesWithoutIncomingEdges.forEach(affectedGraph::removeVertex);
} while (!affectedGraph.vertexSet().isEmpty());

return Optional.of(result);
}

private Group createGroup(final Graph<Microservice, DefaultEdge> graph, final List<Microservice> verticesWithoutIncomingEdges) {
final Group group = new Group();

verticesWithoutIncomingEdges.forEach(v -> {
final Set<DefaultEdge> outgoingEdgesOf = graph.outgoingEdgesOf(v);
final List<Microservice> siblings = new ArrayList<>();
outgoingEdgesOf.forEach(e -> {
final Microservice sibling = graph.getEdgeTarget(e);
siblings.add(sibling);
});

group.addItem(new Item().target(v).siblings(siblings));
});

return group;
}

}
Loading

0 comments on commit 1e21ec1

Please sign in to comment.