Skip to content

Refactors nFlow codebase to use modern Java 17 functional patterns - #687

Open
ravening wants to merge 3 commits into
NitorCreations:masterfrom
ravening:feature/optional-refactors
Open

Refactors nFlow codebase to use modern Java 17 functional patterns#687
ravening wants to merge 3 commits into
NitorCreations:masterfrom
ravening:feature/optional-refactors

Conversation

@ravening

Copy link
Copy Markdown

Summary

Refactors nFlow codebase to use modern Java 17 functional patterns:

Changes

  • Converted imperative loops to streams/forEach in converters and DAO (7 files)
  • Replaced simple null-checks with Optional patterns (6 files)
  • Preserved listener chain semantics with forEach
  • Relaxed Maven enforcer version constraint for local dev

Files Modified

  • nflow-rest-api-common: ListWorkflowDefinitionConverter, CreateWorkflowConverter, ListWorkflowInstanceConverter, StatisticsConverter
  • nflow-engine: WorkflowInstanceDao, ObjectStringMapper, WorkflowInstancePreProcessor, WorkflowStateProcessor, WorkflowDispatcher, WorkflowStateProcessorFactory

Testing

✅ All 355+ tests passing in nflow-engine
✅ All 22+ tests passing in nflow-rest-api-common
✅ Full multi-module build verified

Notes

  • Maven enforcer version temporarily relaxed (3.8.5) for local testing; can be addressed in CI config or docs
  • Changes maintain backward compatibility and multi-DB SQL semantics
  • Functional refactors preserve all side-effects and control flow

});
int updatedRows = 0;
boolean unknownResults = false;
AtomicInteger updatedRows = new AtomicInteger(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not add atomics to place where they are not needed. To export values out of a lambda we usually use

int[] updateRows = { 0 };

and then just do

updatedRows[0] += updateStatus[i];

}
}
return ids;
return Stream.iterate(0, i -> i < updateStatuses.length, i -> i + 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the old code here. Some code checkers say that there is no guarantee that peek is called on a stream. Maybe you could use mapMulti to convert the filter + map options to one?

Also the output list is no longer resized correctly.

StringBuilder sb = new StringBuilder(2000);
for (StackTraceElement element : thread.getStackTrace()) {
sb.append(element).append('\n');
String stack = stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you static import the joining

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to pre-allocate the collector buffer to a large size? By default StringBuilder uses something like 16, which is way small for stack traces (and needs to be doubled many times). Have not checked what is the default for Collectors.joining though.

for (StackTraceElement element : thread.getStackTrace()) {
sb.append(element).append('\n');
String stack = stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n"));
StringBuilder sb = new StringBuilder(stack.length() + 2);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is any point in wrapping the already created string into a StringBuilder again.
Just return String and do not append the final newline at all. Maybe the above logPotentiallyStuck looks ok without it. Or if you want to keep things the same, then just add an extra newline after the getStackTraceAsString parameter.

public void handlePotentiallyStuck(Duration processingTime) {
boolean interrupt = false;
for (WorkflowExecutorListener listener : executorListeners) {
AtomicBoolean interrupt = new AtomicBoolean(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AtomicBoolean interrupt = new AtomicBoolean(false);
boolean[] interrupt = { false };

DateTime currentTime = now();
int potentiallyStuck = 0;
for (WorkflowStateProcessor processor : processingInstances.values()) {
AtomicInteger potentiallyStuck = new AtomicInteger(0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AtomicInteger potentiallyStuck = new AtomicInteger(0);
int[] potentiallyStuck = { 0 };

public WorkflowDefinitionStatisticsResponse convert(Map<String, Map<String, WorkflowDefinitionStatistics>> stats) {
WorkflowDefinitionStatisticsResponse resp = new WorkflowDefinitionStatisticsResponse();
for (Entry<String, Map<String, WorkflowDefinitionStatistics>> entry : stats.entrySet()) {
stats.entrySet().forEach(entry -> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not forEach directly on the map?

action.executionStart, action.executionEnd, action.executorId, stateVariablesToJson(action.updatedStateVariables))
: new Action(action.id, action.type.name(), action.state, action.stateText, action.retryNo,
action.executionStart, action.executionEnd, action.executorId))
.collect(toList());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always prefer using the toList() method directly on the stream, not collector wrapper.

Suggested change
.collect(toList());
.toList();

Collection<State> values = states.values();
resp.states = values.toArray(new State[values.size()]);
definition.getAllowedTransitions().forEach((key, targets) ->
Optional.ofNullable(states.get(key)).ifPresent(s -> s.transitions.addAll(targets)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the ofNullable? the original code assumed the state is consistent.

if (def == null) {
throw new IllegalArgumentException("No workflow definition found for type [" + instance.type + "]");
}
WorkflowDefinition def = Optional.ofNullable(workflowDefinitionService.getWorkflowDefinition(instance.type))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original code was more readable. And also the method continues doing null checks further down.

value = ((Mutable<Object>) value).val;
if (value == null) {
continue;
Optional.ofNullable(value).ifPresent(v -> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use Optional here, then use map method for the conversion and only the final setVariable should be in the ifPresent block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants