get rid of unit of work

This commit is contained in:
Michał Michaluk
2018-02-18 11:56:06 +01:00
parent 35d9dabe9a
commit 95d6b1ed14
16 changed files with 140 additions and 162 deletions

View File

@@ -1,22 +1,26 @@
package pl.com.bottega.factory.demand.forecasting;
import lombok.Value;
import pl.com.bottega.factory.demand.forecasting.DailyDemand.Result;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@Value
public class AdjustDemand {
String refNo;
Map<LocalDate, Adjustment> adjustments;
public void forEachStartingFrom(LocalDate date, BiConsumer<LocalDate, Adjustment> f) {
adjustments.entrySet().stream()
public List<Result> forEachStartingFrom(LocalDate date, BiFunction<LocalDate, Adjustment, Result> f) {
return adjustments.entrySet().stream()
.filter(e -> !e.getKey().isBefore(date))
.forEach(e -> f.accept(e.getKey(), e.getValue()));
.map(e -> f.apply(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
public Optional<LocalDate> latestAdjustment() {

View File

@@ -19,4 +19,8 @@ class Adjustment {
static boolean isStrong(Adjustment adjustment) {
return adjustment != null && adjustment.strong;
}
static boolean isNotStrong(Adjustment adjustment) {
return !isStrong(adjustment);
}
}

View File

@@ -1,11 +1,12 @@
package pl.com.bottega.factory.demand.forecasting;
import lombok.Builder;
import lombok.Value;
import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged.Change;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
class DailyDemand {
@@ -13,59 +14,53 @@ class DailyDemand {
private Demand documented;
private Adjustment adjustment;
private final Events events;
private final ReviewPolicy policy;
interface Events {
void emit(LevelChanged event);
void emit(ToReview event);
void emit(DemandUpdated event);
}
DailyDemand(DailyId id, Events events, ReviewPolicy policy,
DailyDemand(DailyId id, ReviewPolicy policy,
Demand documented, Adjustment adjustment) {
this.id = id;
this.events = events;
this.policy = policy;
this.documented = Optional.ofNullable(documented)
.orElse(Demand.nothingDemanded());
this.adjustment = adjustment;
}
void adjust(Adjustment adjustment) {
Result adjust(Adjustment adjustment) {
Result.ResultBuilder result = Result.builder(id);
State state = state();
this.adjustment = adjustment;
if (state.updated()) {
events.emit(new DemandUpdated(id, documented, adjustment));
result.updated(new DemandUpdated(id, documented, adjustment));
}
if (state.levelChanged()) {
events.emit(new LevelChanged(id, state.getLevelChange()));
result.levelChange(state.getLevelChange());
}
return result.build();
}
void update(Demand documented) {
Result update(Demand documented) {
Result.ResultBuilder result = Result.builder(id);
State state = state();
if (policy.reviewNeeded(this.documented, this.adjustment, documented)) {
events.emit(new ToReview(id,
result.toReview(new ToReview(id,
this.documented,
this.adjustment.getDemand(),
documented)
);
}
if (!Adjustment.isStrong(this.adjustment)) {
if (Adjustment.isNotStrong(this.adjustment)) {
this.adjustment = null;
}
this.documented = documented;
if (state.updated()) {
events.emit(new DemandUpdated(id, documented, adjustment));
result.updated(new DemandUpdated(id, documented, adjustment));
}
if (state.levelChanged()) {
events.emit(new LevelChanged(id, state.getLevelChange()));
result.levelChange(state.getLevelChange());
}
return result.build();
}
Demand getLevel() {
@@ -74,12 +69,6 @@ class DailyDemand {
.orElse(documented);
}
@Value
static class LevelChanged {
DailyId id;
Change change;
}
@Value
static class DemandUpdated {
DailyId id;
@@ -115,4 +104,40 @@ class DailyDemand {
return !level.equals(getLevel());
}
}
@Builder
@Value
static class Result {
DailyId id;
DemandUpdated updated;
DemandedLevelsChanged.Change levelChange;
ReviewRequired.ToReview toReview;
static ResultBuilder builder(DailyId id) {
return new ResultBuilder().id(id);
}
static List<ToReview> reviews(List<Result> results) {
return Collections.unmodifiableList(results.stream()
.filter(result -> result.toReview != null)
.map(result -> result.toReview)
.collect(Collectors.toList()));
}
static Map<DailyId, Change> levelChanges(List<Result> results) {
return Collections.unmodifiableMap(results.stream()
.filter(result -> result.levelChange != null)
.collect(Collectors.toMap(
result -> result.id,
result -> result.levelChange
)));
}
static List<DemandUpdated> updates(List<Result> results) {
return Collections.unmodifiableList(results.stream()
.filter(result -> result.updated != null)
.map(result -> result.updated)
.collect(Collectors.toList()));
}
}
}

View File

@@ -5,8 +5,10 @@ import lombok.Value;
import java.time.Instant;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
@Value
public class Document {
@@ -21,10 +23,11 @@ public class Document {
this.demands = Collections.unmodifiableSortedMap(demands);
}
public void forEachStartingFrom(LocalDate date, BiConsumer<LocalDate, Demand> f) {
demands.entrySet().stream()
public List<DailyDemand.Result> forEachStartingFrom(LocalDate date, BiFunction<LocalDate, Demand, DailyDemand.Result> f) {
return demands.entrySet().stream()
.filter(e -> !e.getKey().isBefore(date))
.forEach(e -> f.accept(e.getKey(), e.getValue()));
.map(e -> f.apply(e.getKey(), e.getValue()))
.collect(Collectors.toList());
}
}

View File

@@ -1,19 +1,24 @@
package pl.com.bottega.factory.demand.forecasting;
import lombok.AllArgsConstructor;
import pl.com.bottega.factory.demand.forecasting.DailyDemand.Result;
import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged.Change;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import pl.com.bottega.factory.product.management.RefNoId;
import java.time.Clock;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
class ProductDemand {
final RefNoId id;
final Demands demands;
final List<DailyDemand.DemandUpdated> updates = new ArrayList<>();
final UnitOfWork unit;
final Clock clock;
final DemandEvents events;
@@ -24,23 +29,34 @@ class ProductDemand {
void adjust(AdjustDemand adjustDemand) {
LocalDate today = LocalDate.now(clock);
adjustDemand.forEachStartingFrom(today, this::adjustDaily);
List<Result> results = adjustDemand
.forEachStartingFrom(today, this::adjustDaily);
updates.addAll(Result.updates(results));
if (unit.anyChanges()) {
events.emit(new DemandedLevelsChanged(id, unit.changes()));
Map<DailyId, Change> changes = Result.levelChanges(results);
if (!changes.isEmpty()) {
events.emit(new DemandedLevelsChanged(id, changes));
}
}
void process(Document document) {
LocalDate today = LocalDate.now(clock);
document.forEachStartingFrom(today, this::updateDaily);
List<Result> results = document
.forEachStartingFrom(today, this::updateDaily);
updates.addAll(Result.updates(results));
if (unit.anyChanges()) {
events.emit(new DemandedLevelsChanged(id, unit.changes()));
Map<DailyId, Change> changes = Result.levelChanges(results);
if (!changes.isEmpty()) {
events.emit(new DemandedLevelsChanged(id, changes));
}
if (unit.anyReviews()) {
events.emit(new ReviewRequired(id, unit.reviews()));
List<ToReview> reviews = Result.reviews(results);
if (!reviews.isEmpty()) {
events.emit(new ReviewRequired(id, reviews));
}
}
@@ -50,13 +66,13 @@ class ProductDemand {
}
}
private void adjustDaily(LocalDate date, Adjustment adjustment) {
private Result adjustDaily(LocalDate date, Adjustment adjustment) {
DailyDemand demand = demands.get(date);
demand.adjust(adjustment);
return demand.adjust(adjustment);
}
private void updateDaily(LocalDate date, Demand demand) {
private Result updateDaily(LocalDate date, Demand demand) {
DailyDemand daily = demands.get(date);
daily.update(demand);
return daily.update(demand);
}
}

View File

@@ -1,49 +0,0 @@
package pl.com.bottega.factory.demand.forecasting;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import java.util.*;
import static java.util.Collections.unmodifiableList;
class UnitOfWork implements DailyDemand.Events {
Map<DailyId, DemandedLevelsChanged.Change> changes = new HashMap<>();
List<ToReview> reviews = new LinkedList<>();
List<DailyDemand.DemandUpdated> updates = new LinkedList<>();
boolean anyChanges() {
return !changes.isEmpty();
}
Map<DailyId, DemandedLevelsChanged.Change> changes() {
return Collections.unmodifiableMap(changes);
}
boolean anyReviews() {
return !reviews.isEmpty();
}
List<ToReview> reviews() {
return Collections.unmodifiableList(reviews);
}
List<DailyDemand.DemandUpdated> updates() {
return unmodifiableList(updates);
}
@Override
public void emit(DailyDemand.LevelChanged event) {
changes.put(event.getId(), event.getChange());
}
@Override
public void emit(ToReview event) {
reviews.add(event);
}
@Override
public void emit(DailyDemand.DemandUpdated event) {
updates.add(event);
}
}

View File

@@ -11,7 +11,6 @@ import static pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview
class DailyDemandBuilder {
Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
DailyDemand.Events events
ReviewPolicy policy = ReviewPolicy.BASIC
String refNo = "3009000"
@@ -20,7 +19,7 @@ class DailyDemandBuilder {
private Adjustment adjustment
DailyDemand build() {
new DailyDemand(new DailyId(refNo, date), events, policy, base, adjustment)
new DailyDemand(new DailyId(refNo, date), policy, base, adjustment)
}
DailyDemandBuilder reset() {
@@ -32,11 +31,6 @@ class DailyDemandBuilder {
clazz == DailyDemand ? build() : super.asType(clazz)
}
DailyDemandBuilder events(DailyDemand.Events events) {
this.events = events
this
}
DailyDemandBuilder nextDate() {
date.plusDays(1)
this
@@ -85,11 +79,8 @@ class DailyDemandBuilder {
new Adjustment(Demand.of(level), false)
}
DailyDemand.LevelChanged levelChanged(long previous, long current) {
new DailyDemand.LevelChanged(
new DailyId(refNo, date),
new Change(Demand.of(previous), Demand.of(current))
)
Change levelChanged(long previous, long current) {
new Change(Demand.of(previous), Demand.of(current))
}
ToReview reviewRequest(long previousDocumented, long adjustment, long newDocumented) {

View File

@@ -77,7 +77,6 @@ class DemandAdjustmentSpec extends Specification implements ProductDemandTrait {
1 * events.emit(levelChanged([2800, 2000], [0, 3500]))
when:
builder.clearUnitOfWork()
demand.adjust(adjustments)
then:

View File

@@ -7,8 +7,8 @@ class DemandsFake extends Demands {
DailyDemandBuilder builder
DemandsFake(String refNo, UnitOfWork unitOfWork, Clock clock) {
this.builder = new DailyDemandBuilder(refNo: refNo, events: unitOfWork, clock: clock)
DemandsFake(String refNo, Clock clock) {
this.builder = new DailyDemandBuilder(refNo: refNo, clock: clock)
fetch = { date -> nothingDemanded(date) }
}

View File

@@ -77,7 +77,6 @@ class DocumentProcessingSpec extends Specification implements ProductDemandTrait
1 * events.emit(levelChanged([2800, 2000], [0, 3500]))
when:
builder.clearUnitOfWork()
demand.process(document)
then:

View File

@@ -5,7 +5,6 @@ import spock.lang.Specification
class KeepingDailyDemandsSpec extends Specification {
def builder = new DailyDemandBuilder()
def events = Mock(DailyDemand.Events)
def "Adjusted demands should be stored"() {
given:
@@ -14,11 +13,12 @@ class KeepingDailyDemandsSpec extends Specification {
.noAdjustments().build()
when:
demand.adjust(adjustDemandTo(3500))
def res = demand.adjust(adjustDemandTo(3500))
then:
demand.getLevel() == Demand.of(3500)
1 * events.emit(levelChanged(2800, 3500))
res.levelChange == levelChanged(2800, 3500)
res.updated != null
}
def "Adjusted demands should be stored when there is no demand for product"() {
@@ -28,11 +28,12 @@ class KeepingDailyDemandsSpec extends Specification {
.noAdjustments().build()
when:
demand.adjust(adjustDemandTo(3500))
def res = demand.adjust(adjustDemandTo(3500))
then:
demand.getLevel() == Demand.of(3500)
1 * events.emit(levelChanged(0, 3500))
res.levelChange == levelChanged(0, 3500)
res.updated != null
}
def "In standard case documented demands overrides adjustments"() {
@@ -42,11 +43,12 @@ class KeepingDailyDemandsSpec extends Specification {
.adjustedTo(3500).build()
when:
demand.update(newCallOffDemand(4000))
def res = demand.update(newCallOffDemand(4000))
then:
demand.getLevel() == Demand.of(4000)
1 * events.emit(levelChanged(3500, 4000))
res.levelChange == levelChanged(3500, 4000)
res.updated != null
}
def "Strong adjustment is kept even after processing of document"() {
@@ -56,11 +58,12 @@ class KeepingDailyDemandsSpec extends Specification {
.stronglyAdjustedTo(3500).build()
when:
demand.update(newCallOffDemand(2800))
def res = demand.update(newCallOffDemand(2800))
then:
demand.getLevel() == Demand.of(3500)
0 * events.emit(_ as DailyDemand.LevelChanged)
res.levelChange == null
res.updated == null
}
def "Document update ignored by strong adjustment should rise warning"() {
@@ -70,15 +73,16 @@ class KeepingDailyDemandsSpec extends Specification {
.stronglyAdjustedTo(3500).build()
when:
demand.update(newCallOffDemand(5000))
def res = demand.update(newCallOffDemand(5000))
then:
demand.getLevel() == Demand.of(3500)
1 * events.emit(reviewRequest(2800, 3500, 5000))
res.toReview == reviewRequest(2800, 3500, 5000)
res.levelChange == null
res.updated != null
}
DailyDemandBuilder demand() {
builder.events = events
builder
}

View File

@@ -10,8 +10,7 @@ import static pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview
class ProductDemandBuilder {
def refNo = "3009000"
def unitOfWork = new UnitOfWork()
def demands = new DemandsFake(refNo, unitOfWork, clock)
def demands = new DemandsFake(refNo, clock)
def clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
DemandEvents events
@@ -39,7 +38,7 @@ class ProductDemandBuilder {
}
def build() {
new ProductDemand(new RefNoId(refNo), demands, unitOfWork, clock, events)
new ProductDemand(new RefNoId(refNo), demands, clock, events)
}
def document(LocalDate date, long ... levels) {
@@ -88,10 +87,4 @@ class ProductDemandBuilder {
Demand.of(strongAdjustment),
Demand.of(newDocumented))
}
void clearUnitOfWork() {
unitOfWork.@changes.clear()
unitOfWork.@reviews.clear()
unitOfWork.@updates.clear()
}
}