Build a very basic workflow engine
which does only really basic stuff and is understood in a second.
- one simple jar to get it running
- no own deployment of a workflow server or any stuff
- reuse of a scheduler framework
- be compatible to other frameworks
- Spring integration
- First Quartz integration
- First Spring integration
- First PlantUML integration
- Wait as own step
- Trigger workflows in an own step
- Link to workflows in repository using
trigger->
- Standalone flow version using a single thread
- Support multiple sub steps if choose
By default this will be configured by spring using the jdbc
store:
spring:
quartz:
job-store-type: jdbc
overwrite-existing-jobs: true
Select latest version: https://search.maven.org/search?q=a:pmw-spring
<dependency>
<groupId>org.sterl.pmw</groupId>
<artifactId>pmw-spring</artifactId>
<version>1.x.x</version>
</dependency>
@Service
@RequiredArgsConstructor
public class NewItemArrivedWorkflow {
private final WarehouseService warehouseService;
private final DiscountComponent discountComponent;
private final WarehouseStockComponent createStock;
private final UpdateInStockCountComponent updateStock;
private final WorkflowService<JobDetail> workflowService;
@Getter
private Workflow<NewItemArrivedWorkflowState> checkWarehouse;
@Getter
private Workflow<NewItemArrivedWorkflowState> restorePriceSubWorkflow;
@PostConstruct
void createWorkflow() {
checkWarehouse = Workflow.builder("check-warehouse", () -> NewItemArrivedWorkflowState.builder().build())
.next("check warehouse for new stock", s -> createStock.checkWarehouseForNewStock(s.getItemId()))
.next("update item stock", (s, c) -> {
final long stockCount = warehouseService.countStock(s.getItemId());
updateStock.updateInStockCount(s.getItemId(), stockCount);
s.setWarehouseStockCount(stockCount);
})
.sleep("Wait if stock is > 40", (s) -> s.getWarehouseStockCount() > 40 ? Duration.ofMinutes(2) : Duration.ZERO)
.choose("check stock", s -> {
if (s.getWarehouseStockCount() > 40) return "discount-price";
else return "check-warehouse-again";
})
.ifSelected("discount-price", "> 40", s -> {
var originalPrice = discountComponent.applyDiscount(s.getItemId(), s.getWarehouseStockCount());
s.setOriginalPrice(originalPrice);
workflowService.execute(restorePriceSubWorkflow, s, Duration.ofMinutes(2));
})
.ifSelected("trigger->restore-item-price", "< 40", s -> this.execute(s.getItemId()))
.build()
.build();
workflowService.register(checkWarehouse);
restorePriceSubWorkflow = Workflow.builder("restore-item-price", () -> NewItemArrivedWorkflowState.builder().build())
.next("set price from workflow state", s -> discountComponent.setPrize(s.getItemId(), s.getOriginalPrice()))
.build();
workflowService.register(restorePriceSubWorkflow);
}
@Transactional(propagation = Propagation.MANDATORY)
public String execute(long itemId) {
return workflowService.execute(checkWarehouse, NewItemArrivedWorkflowState.builder()
.itemId(itemId).build());
}
}
@ExtendWith(MockitoExtension.class)
class NewItemArrivedWorkflowMockTest {
@Mock WarehouseService warehouseService;
@Mock DiscountComponent discountComponent;
@Mock WarehouseStockComponent createStock;
@Mock UpdateInStockCountComponent updateStock;
@Mock WorkflowService<JobDetail> workflowService;
@InjectMocks NewItemArrivedWorkflow subject;
@BeforeEach
void setUp() throws Exception {
subject.createWorkflow();
}
@Test
void testPrintSimple() throws Exception {
SerializationUtil.writeAsPlantUmlSvg("./check-warehouse.svg", subject.getCheckWarehouse());
}
@Test
void testPrintWithSubworkflowSupportByName() throws Exception {
WorkflowRepository repo = new WorkflowRepository();
WorkflowUmlService umlService = new WorkflowUmlService(repo);
repo.register(subject.getCheckWarehouse());
repo.register(subject.getRestorePriceSubWorkflow());
SerializationUtil.writeAsPlantUmlSvg("./check-warehouse.svg", subject.getCheckWarehouse().getName(), umlService);
}
}
- eclipse plugin https://marketplace.eclipse.org/content/plantuml-plugin