domain and design in readme

This commit is contained in:
Michał Michaluk
2017-12-22 16:30:34 +01:00
parent 477149a716
commit 42689ab18d
40 changed files with 184 additions and 174 deletions

View File

@@ -1,4 +1,4 @@
# Missing complete example of Domain-Driven Design enterprise application
# The missing, complete example of Domain-Driven Design enterprise application
## Command Query CRUD Responsibility Segregation
Not every piece of software is equally important...
@@ -63,22 +63,28 @@ Making useful application from the Domain Model and the technology.
In most projects the biggest risk is lack of domain knowledge among developers. We all known Java,
databases and bunch of handy frameworks, but what about: Investment Banking, Automotive Manufacturing or even e-Commerce.
Lets face those risk at first, maintain and explore domain knowledge
Let's face the risk at first, maintain and explore domain knowledge
with **Model Exploration Whirlpool** and build **Ubiquitous Language** with your executable **Domain Model**,
**Domain Stories** and **Specification by Examples** from day one.
Adding infrastructure and technology later is easy thanks to Hexagonal Architecture.
Starting from ZERO business knowledge through initial domain and opportunity exploration with **Big Picture Event Storming**:
<big-picture-es>
Simply starting from ZERO business knowledge through initial domain and opportunity exploration with **Big Picture Event Storming**:
![Big Picture Event Storming](https://github.com/michal-michaluk/factory/raw/master/es-big-picture-original.jpg)
Looking for system boundaries, impacted and required actors and there interactions with system under design:
<actors-and-boundaries>
after cleaning and trimming initial model to most valuable and needed areas:
![Big Picture Event Storming](https://github.com/michal-michaluk/factory/raw/master/es-big-picture-cleaned.jpg)
Estimating depth of domain model and Command Query CRUD segregation:
<command-query-crud>
Deep dive in **Demand Forecasting** sub-domain with **Design Level Event Storming**:
![Design Level Event Storming - Demand Forecasting](https://github.com/michal-michaluk/factory/raw/master/es-design-demand-forecasting.jpg)
Design level Event Storming with Domain Stories and Specification by Examples:
<demand-forecasting-design-es>
<adjust-demand.feature>
is excellent canvas to cooperative exploration of:
- impacted and required actors,
- initial / desired system boundaries,
- actors interactions with system under design.
<shortage-prediction-design-es>
With use of **Domain Stories** and **Specification by Examples** it is easy to find:
- business rules and invariants,
- acceptance criteria,
- estimation of Domain Model depth,
- CRUD-suspected activities,
- missing parts.

View File

@@ -32,7 +32,7 @@ class DemandEventsMapping implements DemandEvents {
}
@Override
public void emit(ReviewRequested event) {
public void emit(ReviewRequired event) {
Instant timestamp = Instant.now(clock);
demandReviews.save(event.getReviews().stream()
.map(r -> new DemandReviewEntity(timestamp, r))

View File

@@ -21,7 +21,7 @@ import static java.util.stream.Collectors.toMap;
@Component
@AllArgsConstructor
class ForecastORMRepository implements Forecasts {
class ForecastORMRepository implements ShortageForecasts {
private final WarehouseService stocks;
private final DeliveryForecastDao deliveries;
@@ -29,7 +29,7 @@ class ForecastORMRepository implements Forecasts {
private final Clock clock;
@Override
public Forecast get(RefNoId refNo, int daysAhead) {
public ShortageForecast get(RefNoId refNo, int daysAhead) {
Stock stock = stocks.forRefNo(refNo);
LocalDateTime time = LocalDateTime.now(clock);
LocalDateTime max = time.plusDays(daysAhead).truncatedTo(ChronoUnit.DAYS);
@@ -53,6 +53,6 @@ class ForecastORMRepository implements Forecasts {
.collect(Collectors.toList())
).outputsInTimes(time, deliveryTimes);
return new Forecast(refNo.getRefNo(), time, deliveryTimes, stock, outputs, demand);
return new ShortageForecast(refNo.getRefNo(), time, deliveryTimes, stock, outputs, demand);
}
}

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.demand.forecasting;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import javax.transaction.Transactional;
@@ -29,7 +29,7 @@ public class DemandService {
repository.save(model);
}
public void review(ReviewNeeded review, ReviewDecision decision) {
public void review(ToReview review, ReviewDecision decision) {
ProductDemand model = repository.get(review.getRefNo());
model.review(review, decision);
repository.save(model);

View File

@@ -4,7 +4,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import pl.com.bottega.factory.demand.forecasting.ReviewDecision;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import pl.com.bottega.tools.JsonConverter;
import javax.persistence.*;
@@ -25,14 +25,14 @@ public class DemandReviewEntity implements Serializable {
private LocalDate date;
private Instant timestamp;
@Convert(converter = ReviewAsJson.class)
private ReviewNeeded review;
private ToReview review;
@Enumerated(EnumType.STRING)
private ReviewDecision decision;
@Setter
private LocalDate cleanAfter;
public DemandReviewEntity(Instant timestamp, ReviewNeeded review) {
public DemandReviewEntity(Instant timestamp, ToReview review) {
this.timestamp = timestamp;
this.refNo = review.getId().getRefNo();
this.date = review.getId().getDate();
@@ -43,9 +43,9 @@ public class DemandReviewEntity implements Serializable {
return decision != null;
}
public static class ReviewAsJson extends JsonConverter<ReviewNeeded> {
public static class ReviewAsJson extends JsonConverter<ToReview> {
public ReviewAsJson() {
super(ReviewNeeded.class);
super(ToReview.class);
}
}
}

View File

@@ -7,7 +7,7 @@ import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import org.springframework.scheduling.annotation.EnableScheduling;
import pl.com.bottega.factory.demand.forecasting.DemandEvents;
import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired;
import java.time.Clock;
@@ -34,7 +34,7 @@ public class Configuration {
}
@Override
public void emit(ReviewRequested event) {
public void emit(ReviewRequired event) {
}
}

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.demand.forecasting;
import lombok.Value;
import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged.Change;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import java.util.Objects;
import java.util.Optional;
@@ -19,7 +19,7 @@ class DailyDemand {
interface Events {
void emit(LevelChanged event);
void emit(ReviewNeeded event);
void emit(ToReview event);
void emit(DemandUpdated event);
}
@@ -49,7 +49,7 @@ class DailyDemand {
void update(Demand documented) {
State state = state();
if (policy.reviewNeeded(this.documented, this.adjustment, documented)) {
events.emit(new ReviewNeeded(id,
events.emit(new ToReview(id,
this.documented,
this.adjustment.getDemand(),
documented)

View File

@@ -3,5 +3,5 @@ package pl.com.bottega.factory.demand.forecasting;
public interface DemandEvents {
void emit(DemandedLevelsChanged event);
void emit(ReviewRequested event);
void emit(ReviewRequired event);
}

View File

@@ -1,7 +1,7 @@
package pl.com.bottega.factory.demand.forecasting;
import lombok.AllArgsConstructor;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import pl.com.bottega.factory.product.management.RefNoId;
import java.time.Clock;
@@ -40,11 +40,11 @@ class ProductDemand {
events.emit(new DemandedLevelsChanged(id, unit.changes()));
}
if (unit.anyReviews()) {
events.emit(new ReviewRequested(id, unit.reviews()));
events.emit(new ReviewRequired(id, unit.reviews()));
}
}
void review(ReviewNeeded review, ReviewDecision decision) {
void review(ToReview review, ReviewDecision decision) {
if (decision.requireAdjustment()) {
adjust(decision.toAdjustment(review));
}

View File

@@ -1,7 +1,7 @@
package pl.com.bottega.factory.demand.forecasting;
import lombok.AllArgsConstructor;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import java.util.Collections;
import java.util.function.Function;
@@ -9,13 +9,13 @@ import java.util.function.Function;
@AllArgsConstructor
public enum ReviewDecision {
IGNORE(r -> null),
PICK_PREVIOUS(ReviewNeeded::getPreviousDocumented),
MAKE_ADJUSTMENT_WEAK(ReviewNeeded::getAdjustment),
PICK_NEW(ReviewNeeded::getNewDocumented);
PICK_PREVIOUS(ToReview::getPreviousDocumented),
MAKE_ADJUSTMENT_WEAK(ToReview::getAdjustment),
PICK_NEW(ToReview::getNewDocumented);
private final Function<ReviewNeeded, Demand> pick;
private final Function<ToReview, Demand> pick;
public AdjustDemand toAdjustment(ReviewNeeded review) {
public AdjustDemand toAdjustment(ToReview review) {
if (this == IGNORE) {
throw new IllegalStateException("can't convert " + this + " to adjustment");
}

View File

@@ -1,6 +1,6 @@
package pl.com.bottega.factory.demand.forecasting;
import pl.com.bottega.factory.demand.forecasting.ReviewRequested.ReviewNeeded;
import pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview;
import java.util.*;
@@ -9,7 +9,7 @@ import static java.util.Collections.unmodifiableList;
class UnitOfWork implements DailyDemand.Events {
Map<DailyId, DemandedLevelsChanged.Change> changes = new HashMap<>();
List<ReviewNeeded> reviews = new LinkedList<>();
List<ToReview> reviews = new LinkedList<>();
List<DailyDemand.DemandUpdated> updates = new LinkedList<>();
boolean anyChanges() {
@@ -24,7 +24,7 @@ class UnitOfWork implements DailyDemand.Events {
return !reviews.isEmpty();
}
List<ReviewNeeded> reviews() {
List<ToReview> reviews() {
return Collections.unmodifiableList(reviews);
}
@@ -38,7 +38,7 @@ class UnitOfWork implements DailyDemand.Events {
}
@Override
public void emit(ReviewNeeded event) {
public void emit(ToReview event) {
reviews.add(event);
}

View File

@@ -6,7 +6,7 @@ import java.time.LocalDate
import java.time.ZoneId
import static DemandedLevelsChanged.Change
import static ReviewRequested.ReviewNeeded
import static pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview
class DailyDemandBuilder {
@@ -92,8 +92,8 @@ class DailyDemandBuilder {
)
}
ReviewNeeded reviewRequest(long previousDocumented, long adjustment, long newDocumented) {
new ReviewNeeded(
ToReview reviewRequest(long previousDocumented, long adjustment, long newDocumented) {
new ToReview(
new DailyId(refNo, date),
Demand.of(previousDocumented),
Demand.of(adjustment),

View File

@@ -5,7 +5,7 @@ import pl.com.bottega.factory.product.management.RefNoId
import java.time.*
import static DemandedLevelsChanged.Change
import static ReviewRequested.ReviewNeeded
import static pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview
class ProductDemandBuilder {
@@ -74,15 +74,15 @@ class ProductDemandBuilder {
new DemandedLevelsChanged(new RefNoId(refNo), results)
}
ReviewRequested reviewRequest(ReviewNeeded... reviews) {
new ReviewRequested(new RefNoId(refNo), reviews as List)
ReviewRequired reviewRequest(ToReview... reviews) {
new ReviewRequired(new RefNoId(refNo), reviews as List)
}
ReviewNeeded review(LocalDate date,
long previousDocumented,
long strongAdjustment,
long newDocumented) {
new ReviewNeeded(
ToReview review(LocalDate date,
long previousDocumented,
long strongAdjustment,
long newDocumented) {
new ToReview(
new DailyId(refNo, date),
Demand.of(previousDocumented),
Demand.of(strongAdjustment),

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.demand.forecasting
import java.time.LocalDate
import static ReviewRequested.ReviewNeeded
import static pl.com.bottega.factory.demand.forecasting.ReviewRequired.ToReview
trait ProductDemandTrait {
@@ -32,11 +32,11 @@ trait ProductDemandTrait {
[]
}
ReviewRequested reviewRequest(ReviewNeeded... reviews) {
ReviewRequired reviewRequest(ToReview... reviews) {
builder.reviewRequest(reviews)
}
ReviewNeeded review(
ToReview review(
LocalDate date,
long previousDocumented,
long strongAdjustment,

View File

@@ -1,25 +1,29 @@
Feature: manual adjustments of demand
sub domain: demand forecasting
Sub Domain: demand forecasting
keeps track of current and future customer needs for our products
Domain story:
Adjust demand at day to amount, delivered.
>> demand.adjust(productRefNo, atDay, amount)
We can change only Demands for today and future.
New demand is stored for further reference
Data from call-off document should be preserved (DONT OVERRIDE THEM).
Data from call-off document should be preserved.
Adjust demand should be possible even
if there was no call-off document for that product.
In standard case future call-off documents should be stronger (overrides) adjustment,
but if customer warn us about opposite case import of call-off document should not remove previous adjustments.
if there was no document for that product.
In standard case future call-off documents should override adjustment,
but if customer warn us about opposite case
import of document should not remove previous adjustments.
Logistician note should be kept with adjustment.
emit domain event demand changed
Domain event: demanded levels changed
[context boundary]
Logistician note should be kept along with adjustment.
Sub Domain: shortage prediction
continuously monitors demands, production plan and stock levels
predicts potential shortage based on forecasts
notifies personal about potential shortages
outside of context boundary:
If new demand is not fulfilled by
current product stock and production forecast
there is a shortage in particular days and we need to rise an alert.

BIN
es-big-picture-cleaned.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

BIN
es-big-picture-original.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

View File

@@ -8,17 +8,17 @@ import java.util.Collections;
import java.util.List;
@Value
public class ReviewRequested {
public class ReviewRequired {
RefNoId refNo;
List<ReviewNeeded> reviews;
List<ToReview> reviews;
public ReviewRequested(RefNoId refNo, List<ReviewNeeded> reviews) {
public ReviewRequired(RefNoId refNo, List<ToReview> reviews) {
this.refNo = refNo;
this.reviews = Collections.unmodifiableList(reviews);
}
@Value
public static class ReviewNeeded {
public static class ToReview {
DailyId id;
Demand previousDocumented;
Demand adjustment;

View File

@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.factory.shortages.prediction.Configuration;
import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts;
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts;
import pl.com.bottega.factory.shortages.prediction.monitoring.persistence.ShortagesDao;
import pl.com.bottega.factory.shortages.prediction.monitoring.persistence.ShortagesEntity;
import pl.com.bottega.tools.TechnicalId;
@@ -17,7 +17,7 @@ class ShortagePredictionProcessORMRepository {
private final ShortagesDao dao;
private final ShortageDiffPolicy policy = ShortageDiffPolicy.ValuesAreNotSame;
private final Forecasts forecasts;
private final ShortageForecasts forecasts;
private final Configuration configuration = () -> 14;
private final ShortageEvents events;
@@ -40,7 +40,7 @@ class ShortagePredictionProcessORMRepository {
ShortagesEntity entity = TechnicalId.findOrDefault(
refNo, dao::findOne,
() -> dao.save(new ShortagesEntity(refNo.getRefNo())));
entity.setShortages(event.getShortages());
entity.setShortages(event.getShortage());
events.emit(event);
}

View File

@@ -4,7 +4,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import pl.com.bottega.tools.JsonConverter;
import pl.com.bottega.tools.TechnicalId;
@@ -25,7 +25,7 @@ public class ShortagesEntity implements Serializable {
private String refNo;
@Setter
@Convert(converter = ShortagesAsJson.class)
private Shortages shortages;
private Shortage shortages;
public ShortagesEntity(String refNo) {
this.refNo = refNo;
@@ -39,9 +39,9 @@ public class ShortagesEntity implements Serializable {
return id instanceof ShortagesEntityId ? id : new ShortagesEntityId(id.getRefNo());
}
public static class ShortagesAsJson extends JsonConverter<Shortages> {
public static class ShortagesAsJson extends JsonConverter<Shortage> {
public ShortagesAsJson() {
super(Shortages.class);
super(Shortage.class);
}
}

View File

@@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.notification;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import java.time.Clock;
@@ -27,17 +27,17 @@ public class NotificationConfiguration {
private static class MockedPlannerPushNotifications implements Notifications {
@Override
public void alertPlanner(Shortages shortage) {
public void alertPlanner(Shortage shortage) {
}
@Override
public void softNotifyPlanner(Shortages shortage) {
public void softNotifyPlanner(Shortage shortage) {
}
@Override
public void markOnPlan(Shortages shortage) {
public void markOnPlan(Shortage shortage) {
}
}

View File

@@ -6,8 +6,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import org.springframework.scheduling.annotation.EnableScheduling;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.factory.shortages.prediction.calculation.Forecast;
import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts;
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecast;
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts;
import pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage;
import pl.com.bottega.factory.shortages.prediction.monitoring.ShortageEvents;
import pl.com.bottega.factory.shortages.prediction.monitoring.ShortageSolved;
@@ -26,8 +26,8 @@ public class Configuration {
}
@Bean
public Forecasts forecasts() {
return new ForecastsFake();
public ShortageForecasts forecasts() {
return new ShortageForecastsFake();
}
@Bean
@@ -47,9 +47,9 @@ public class Configuration {
}
}
private class ForecastsFake implements Forecasts {
private class ShortageForecastsFake implements ShortageForecasts {
@Override
public Forecast get(RefNoId refNo, int daysAhead) {
public ShortageForecast get(RefNoId refNo, int daysAhead) {
return null;
}
}

View File

@@ -4,8 +4,8 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.Commit
import pl.com.bottega.factory.product.management.RefNoId
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts
import pl.com.bottega.factory.shortages.prediction.Shortage
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts
import pl.com.bottega.factory.shortages.prediction.monitoring.persistence.ShortagesDao
import pl.com.bottega.factory.shortages.prediction.monitoring.persistence.ShortagesEntity
import spock.lang.Specification
@@ -25,7 +25,7 @@ class ShortagePredictionProcessORMRepositoryTest extends Specification {
@Autowired
ShortagesDao dao
def forecasts = Mock(Forecasts)
def forecasts = Mock(ShortageForecasts)
def notifications = Mock(ShortageEvents)
ShortagePredictionProcessORMRepository repository
@@ -88,13 +88,13 @@ class ShortagePredictionProcessORMRepositoryTest extends Specification {
noShortagesPersisted()
}
def persistedShortage(Shortages shortages) {
def persistedShortage(Shortage shortages) {
def entity = new ShortagesEntity(refNo)
entity.setShortages(shortages)
dao.save(entity)
}
Shortages shortagesCurrentlyPersisted() {
Shortage shortagesCurrentlyPersisted() {
dao.findByRefNo(refNo).get().shortages
}
@@ -106,29 +106,29 @@ class ShortagePredictionProcessORMRepositoryTest extends Specification {
repository.get(new RefNoId(refNo))
}
Shortages noShortages() {
Shortage noShortages() {
null
}
Shortages someShortages() {
Shortages.builder(refNo, 0, now)
Shortage someShortages() {
Shortage.builder(refNo, 0, now)
.missing(now.plusDays(1), 500)
.build()
.orElse(null)
}
Shortages someOldShortages() {
Shortages.builder(refNo, 0, now.minusDays(1))
Shortage someOldShortages() {
Shortage.builder(refNo, 0, now.minusDays(1))
.missing(now.plusDays(2), 2500)
.build()
.orElse(null)
}
Shortages shortagesCurrentlyKnownBy(ShortagePredictionProcess process) {
Shortage shortagesCurrentlyKnownBy(ShortagePredictionProcess process) {
process.known
}
void processEmitsNewShortage(ShortagePredictionProcess process, Shortages shortages) {
void processEmitsNewShortage(ShortagePredictionProcess process, Shortage shortages) {
process.events.emit(new NewShortage(process.refNo, DemandChanged, shortages))
}

View File

@@ -15,22 +15,22 @@ import java.util.TreeMap;
* Created by michal on 22.10.2015.
*/
@Value
public class Shortages {
public class Shortage {
private final String refNo;
private final long lockedParts;
private final LocalDateTime found;
private final SortedMap<LocalDateTime, Long> shortages;
public static Shortages.Builder builder(String refNo, long locked, LocalDateTime found) {
public static Shortage.Builder builder(String refNo, long locked, LocalDateTime found) {
return new Builder(refNo, locked, found);
}
public static boolean areNotSame(Shortages first, Shortages second) {
public static boolean areNotSame(Shortage first, Shortage second) {
return !areSame(first, second);
}
public static boolean areSame(Shortages first, Shortages second) {
public static boolean areSame(Shortage first, Shortage second) {
boolean noShortages = first == null && second == null;
boolean onlyOne = first == null && second != null || first != null && second == null;
if (noShortages || onlyOne) return false;
@@ -55,11 +55,11 @@ public class Shortages {
return this;
}
public Optional<Shortages> build() {
public Optional<Shortage> build() {
if (gaps.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(new Shortages(refNo, locked, found,
return Optional.of(new Shortage(refNo, locked, found,
Collections.unmodifiableSortedMap(gaps)));
}
}

View File

@@ -1,14 +1,14 @@
package pl.com.bottega.factory.shortages.prediction.calculation;
import lombok.AllArgsConstructor;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.SortedSet;
@AllArgsConstructor
public class Forecast {
public class ShortageForecast {
private final String refNo;
private final LocalDateTime created;
@@ -17,10 +17,10 @@ public class Forecast {
private final ProductionOutputs outputs;
private final DeliveriesForecast deliveries;
public Optional<Shortages> findShortages() {
public Optional<Shortage> findShortages() {
long level = stock.getLevel();
Shortages.Builder found = Shortages.builder(refNo, stock.getLocked(), created);
Shortage.Builder found = Shortage.builder(refNo, stock.getLocked(), created);
LocalDateTime lastTime = created;
for (LocalDateTime time : deliveryTimes) {
long demand = deliveries.get(time);

View File

@@ -2,6 +2,6 @@ package pl.com.bottega.factory.shortages.prediction.calculation;
import pl.com.bottega.factory.product.management.RefNoId;
public interface Forecasts {
Forecast get(RefNoId refNo, int daysAhead);
public interface ShortageForecasts {
ShortageForecast get(RefNoId refNo, int daysAhead);
}

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.shortages.prediction.monitoring;
import lombok.Value;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
/**
* Created by michal on 03.02.2017.
@@ -14,5 +14,5 @@ public class NewShortage {
RefNoId refNo;
After trigger;
Shortages shortages;
Shortage shortage;
}

View File

@@ -1,10 +1,10 @@
package pl.com.bottega.factory.shortages.prediction.monitoring;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
interface ShortageDiffPolicy {
ShortageDiffPolicy ValuesAreNotSame = Shortages::areNotSame;
ShortageDiffPolicy ValuesAreNotSame = Shortage::areNotSame;
boolean areDifferent(Shortages previous, Shortages found);
boolean areDifferent(Shortage previous, Shortage found);
}

View File

@@ -3,9 +3,9 @@ package pl.com.bottega.factory.shortages.prediction.monitoring;
import lombok.AllArgsConstructor;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.factory.shortages.prediction.Configuration;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.calculation.Forecast;
import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecast;
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts;
import pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage.After;
import java.util.Optional;
@@ -17,10 +17,10 @@ import java.util.Optional;
class ShortagePredictionProcess {
private final RefNoId refNo;
private Shortages known;
private Shortage known;
private final ShortageDiffPolicy diffPolicy;
private final Forecasts forecasts;
private final ShortageForecasts forecasts;
private final Configuration configuration;
private final ShortageEvents events;
@@ -41,10 +41,10 @@ class ShortagePredictionProcess {
}
private void predict(After event) {
Forecast forecast = forecasts.get(refNo,
ShortageForecast forecast = forecasts.get(refNo,
configuration.shortagePredictionDaysAhead());
Optional<Shortages> newlyFound = forecast.findShortages();
Optional<Shortage> newlyFound = forecast.findShortages();
boolean areDifferent = diffPolicy.areDifferent(this.known, newlyFound.orElse(null));
if (areDifferent && newlyFound.isPresent()) {

View File

@@ -4,7 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Singular;
import lombok.Value;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage;
import pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage.After;
@@ -32,9 +32,9 @@ public class NotificationOfShortage {
}
public void notifyAbout(NewShortage event) {
Shortages shortage = event.getShortages();
Shortage shortage = event.getShortage();
rules.wayOfNotificationAfter(event.getTrigger())
.notifyAbout(event.getShortages());
.notifyAbout(event.getShortage());
if (policy.shouldIncreasePriority(LocalDateTime.now(clock), shortage)) {
qualityTasks.increasePriorityFor(shortage.getRefNo());
@@ -54,6 +54,6 @@ public class NotificationOfShortage {
}
interface Notificator {
void notifyAbout(Shortages shortages);
void notifyAbout(Shortage shortage);
}
}

View File

@@ -1,14 +1,14 @@
package pl.com.bottega.factory.shortages.prediction.notification;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
/**
* Created by michal on 02.02.2017.
*/
public interface Notifications {
void alertPlanner(Shortages shortage);
void alertPlanner(Shortage shortage);
void softNotifyPlanner(Shortages shortage);
void softNotifyPlanner(Shortage shortage);
void markOnPlan(Shortages shortage);
void markOnPlan(Shortage shortage);
}

View File

@@ -1,6 +1,6 @@
package pl.com.bottega.factory.shortages.prediction.notification;
import pl.com.bottega.factory.shortages.prediction.Shortages;
import pl.com.bottega.factory.shortages.prediction.Shortage;
import java.time.LocalDateTime;
@@ -10,7 +10,7 @@ import java.time.LocalDateTime;
public interface RecoveryTaskPriorityChangePolicy {
static RecoveryTaskPriorityChangePolicy never() {
return (LocalDateTime now, Shortages shortage) -> false;
return (LocalDateTime now, Shortage shortage) -> false;
}
static RecoveryTaskPriorityChangePolicy onlyIn1DaysAhead() {
@@ -18,10 +18,10 @@ public interface RecoveryTaskPriorityChangePolicy {
}
static RecoveryTaskPriorityChangePolicy shortageInDays(long shortageInXDays) {
return (LocalDateTime now, Shortages shortage) ->
return (LocalDateTime now, Shortage shortage) ->
shortage.getLockedParts() > 0 && shortage.anyBefore(
now.plusDays(shortageInXDays));
}
boolean shouldIncreasePriority(LocalDateTime now, Shortages shortage);
boolean shouldIncreasePriority(LocalDateTime now, Shortage shortage);
}

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation
import spock.lang.Specification
class ShortagesCalculationAlgorithmSpec extends Specification
class ShortageCalculationAlgorithmSpec extends Specification
implements ShortagesCalculationAssemblerTrait {
void setup() {

View File

@@ -2,7 +2,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation
import spock.lang.Specification
class ShortagesCalculationExamplesSpec extends Specification
class ShortageCalculationExamplesSpec extends Specification
implements ShortagesCalculationAssemblerTrait {
void setup() {

View File

@@ -1,7 +1,7 @@
package pl.com.bottega.factory.shortages.prediction.calculation
import pl.com.bottega.factory.product.management.RefNoId
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.Shortage
import java.time.Duration
import java.time.LocalDateTime
@@ -12,13 +12,13 @@ trait ShortagesCalculationAssemblerTrait {
String refNo = "3009000"
SortedSet<LocalDateTime> times
Forecasts forecastProvider(Stock stock, DeliveriesForecast demands, ProductionOutputs outputs) {
ShortageForecasts forecastProvider(Stock stock, DeliveriesForecast demands, ProductionOutputs outputs) {
def forecast = forecast(stock, demands, outputs)
return { RefNoId refNo, int daysAhead -> forecast } as Forecasts
return { RefNoId refNo, int daysAhead -> forecast } as ShortageForecasts
}
Forecast forecast(Stock stock, DeliveriesForecast demands, ProductionOutputs outputs) {
new Forecast(refNo, now, times, stock, outputs, demands)
ShortageForecast forecast(Stock stock, DeliveriesForecast demands, ProductionOutputs outputs) {
new ShortageForecast(refNo, now, times, stock, outputs, demands)
}
ProductionOutputs noProductions() {
@@ -53,12 +53,12 @@ trait ShortagesCalculationAssemblerTrait {
new Stock(level, locked)
}
Optional<Shortages> noShortages() {
Optional<Shortage> noShortages() {
Optional.empty()
}
Optional<Shortages> shortage(Map<LocalDateTime, Long> missing, long locked = 0) {
def shortages = Shortages.builder(refNo, locked, now)
Optional<Shortage> shortage(Map<LocalDateTime, Long> missing, long locked = 0) {
def shortages = Shortage.builder(refNo, locked, now)
missing.each { time, level -> shortages.missing(time, level) }

View File

@@ -1,6 +1,6 @@
package pl.com.bottega.factory.shortages.prediction.monitoring
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.Shortage
import pl.com.bottega.factory.shortages.prediction.calculation.TimeGrammar
import spock.lang.Specification
@@ -18,11 +18,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now + 5.min)
Shortage another = Shortage.builder("3009000", 0, now + 5.min)
.missing(now + 1.day, 500L)
.build().orElse(null)
@@ -34,11 +34,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 1000, now)
Shortage another = Shortage.builder("3009000", 1000, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
@@ -50,11 +50,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000XXX", 0, now)
Shortage one = Shortage.builder("3009000XXX", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now)
Shortage another = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
@@ -66,11 +66,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now)
Shortage another = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 1500L)
.build().orElse(null)
@@ -82,11 +82,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now)
Shortage another = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 499L)
.build().orElse(null)
@@ -98,11 +98,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now)
Shortage another = Shortage.builder("3009000", 0, now)
.missing(now + 2.day, 500L)
.build().orElse(null)
@@ -114,11 +114,11 @@ class ShortageDiffPolicySpec extends Specification {
given:
def policy = ShortageDiffPolicy.ValuesAreNotSame
Shortages one = Shortages.builder("3009000", 0, now)
Shortage one = Shortage.builder("3009000", 0, now)
.missing(now + 1.day, 500L)
.build().orElse(null)
Shortages another = Shortages.builder("3009000", 0, now)
Shortage another = Shortage.builder("3009000", 0, now)
.missing(now + 1.day + 1.min, 500L)
.build().orElse(null)

View File

@@ -2,8 +2,8 @@ package pl.com.bottega.factory.shortages.prediction.monitoring
import pl.com.bottega.factory.product.management.RefNoId
import pl.com.bottega.factory.shortages.prediction.Configuration
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts
import pl.com.bottega.factory.shortages.prediction.Shortage
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts
import pl.com.bottega.factory.shortages.prediction.calculation.ShortagesCalculationAssembler
import spock.lang.Specification
@@ -119,8 +119,8 @@ class ShortagePredictionProcessSpec extends Specification {
}
ShortagePredictionProcess predictionProcess(
Shortages previouslyFound,
Forecasts forecastThatWillFindShortages) {
Shortage previouslyFound,
ShortageForecasts forecastThatWillFindShortages) {
new ShortagePredictionProcess(
refNo,
@@ -142,15 +142,15 @@ class ShortagePredictionProcessSpec extends Specification {
(now.plusDays(1)) : 900L]
}
Shortages noShortagesWasPreviouslyFound() {
Shortage noShortagesWasPreviouslyFound() {
null
}
Shortages wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
Shortage wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
forecastAssembler.shortage(shortages).orElse(null)
}
Forecasts noShortagesWillBeFound() {
ShortageForecasts noShortagesWillBeFound() {
forecastAssembler.forecastProvider(
forecastAssembler.stock(1000),
forecastAssembler.noDeliveries(),
@@ -158,7 +158,7 @@ class ShortagePredictionProcessSpec extends Specification {
)
}
Forecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
ShortageForecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
forecastAssembler.forecastProvider(
forecastAssembler.stock(0),
forecastAssembler.deliveries(shortages),

View File

@@ -1,7 +1,7 @@
package pl.com.bottega.factory.shortages.prediction.notification
import pl.com.bottega.factory.product.management.RefNoId
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.Shortage
import pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage
import spock.lang.Specification
@@ -109,14 +109,14 @@ class NotificationOfShortageSpec extends Specification {
)
}
NewShortage newShortage(After after, Shortages shortages) {
NewShortage newShortage(After after, Shortage shortages) {
new NewShortage(new RefNoId(refNo), after, shortages)
}
Shortages withShortage(
Shortage withShortage(
Duration firstShortageIn = Duration.ofDays(4),
long lockedStock = 0) {
Shortages.builder(refNo, lockedStock, now)
Shortage.builder(refNo, lockedStock, now)
.missing(now.plus(firstShortageIn), 500L)
.build().get()
}

View File

@@ -1,6 +1,6 @@
package pl.com.bottega.factory.shortages.prediction.notification
import pl.com.bottega.factory.shortages.prediction.Shortages
import pl.com.bottega.factory.shortages.prediction.Shortage
import spock.lang.Specification
import java.time.Duration
@@ -10,8 +10,8 @@ class RecoveryTaskPriorityChangePolicySpec extends Specification {
def now = LocalDateTime.now()
Shortages foundShortage(Duration firstShortageIn, long lockedStock) {
Shortages.builder("3009000", lockedStock, now)
Shortage foundShortage(Duration firstShortageIn, long lockedStock) {
Shortage.builder("3009000", lockedStock, now)
.missing(now.plus(firstShortageIn), 500L)
.build().get()
}