Skip to content

Commit

Permalink
build: initial content (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dennis-Koch authored and Dennis Koch committed Apr 26, 2024
1 parent 586cd62 commit 4c8dcb1
Show file tree
Hide file tree
Showing 127 changed files with 11,547 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@Dennis-Koch

/.github @Dennis-Koch
54 changes: 54 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ℹ️ The main workflow used to automatically build your application, run tests,
# execute static analysis on your code and deploy it to Nexus if all steps
# succeed.
#
# This workflow is triggered on every push and every pull request opened to
# your repository.
#
#
name: Build

on:
push:
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:

jobs:
build:
runs-on: ubuntu-latest

permissions:
packages: write

steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
server-id: github
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- name: prepare local git
run: git fetch --unshallow
- name: resolve dependencies
env:
MAVEN_USERNAME: Dennis-Koch
MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
run: ./mvnw -B dependency:go-offline -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
- name: build & deploy
env:
MAVEN_USERNAME: Dennis-Koch
MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
SIGN_KEY_ID: ${{ secrets.MVN_SIGN_KEY_ID }}
SIGN_KEY: ${{ secrets.MVN_SIGN_KEY }}
SIGN_KEY_PASS: ${{ secrets.MVN_SIGN_KEY_PASS }}
run: ./mvnw -B deploy -Dsign.skipNoKey=false
# -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
#- name: Update dependency graph
# uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.classpath
.factorypath
.idea
.project
.settings
.flattened-pom.xml
.git-versioned-pom.xml
target
*.gz
*.iml
*.jar
*.tar
*.war
*.zip
/.mvn/maven.config
7 changes: 7 additions & 0 deletions .mvn/extensions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<extensions xmlns="https://maven.apache.org/EXTENSIONS/1.0.0">
<extension>
<groupId>me.qoomon</groupId>
<artifactId>maven-git-versioning-extension</artifactId>
<version>9.7.0</version>
</extension>
</extensions>
21 changes: 21 additions & 0 deletions .mvn/maven-git-versioning-extension.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<configuration xmlns="https://github.com/qoomon/maven-git-versioning-extension"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/qoomon/maven-git-versioning-extension https://qoomon.github.io/maven-git-versioning-extension/configuration-7.0.0.xsd">

<disable>false</disable>
<refs considerTagsOnBranches="true">
<ref type="tag">
<describeTagPattern>(.+)</describeTagPattern>
<version>${describe.tag.version}${dirty}${dirty.snapshot}</version>
</ref>

<ref type="branch">
<describeTagPattern>(.+)</describeTagPattern>
<version>${describe.tag.version.major}.${describe.tag.version.minor}.${describe.tag.version.patch.next}-${ref.slug}-SNAPSHOT</version>
</ref>
</refs>

<rev>
<version>${commit}</version>
</rev>
</configuration>
18 changes: 18 additions & 0 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
Empty file added CHANGELOG.md
Empty file.
187 changes: 186 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,186 @@
# threadly-streaming
# threadly-streaming

## Key features of this library

### 1) [Chain-Revert API](#chain-revert-api)

### 2) [Bullet-proof Java Parallel-Streaming API](#parallel-streaming-api)

---
A very simple example might look like this:

# <a name="chain-revert-api"></a>Chain-Revert API

This small but powerful API is intended to help in usecases where you have a state transition in your system that you
want to make _revertable_. Revertable means here that we want to restore the state before said transition, similar to a
transaction rollback (but much more lightweight). In other words we intend to apply the
[stack pattern](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) to your state transition in a thread-local and
re-entrant manner. As a result of such a capability you can easily chain also multiple revertable state transitions into
one easy to handle _revertable_. While doing these we still ensure that internal implementation details of a component
that describe how to revert a state is not disclosed to the caller.

### try 1: example without threadly-streaming

```java
setFooState(fooValue1);

run_logic_that_works_with_state_of_foo();

clearFooState();
```

**This common approach has a major robustness flaw:**

If an error occurs during the execution of `run_logic_that_works_with_state_of_foo()` then `clearFooState()` is not
invoked and we may have a memory leak or might even expose sensitive customer data to other requests served by the same
thread in successor tasks

So lets try a better version - still without dedicated library support...

### try 2: example without threadly-streaming, but having solved flaw #1

```java
setFooState(fooValue1);
try{

run_logic_that_works_with_state_of_foo();
}finally{

clearFooState();
}
```

**This approach still has a major robustness issues:** It is not reentrant - This means that multiple executions of the
same method body by the same thread may corrupt the underlying thread-local state!

Imagine the following sequence

```
setFooState(fooValue1) // here fooValue1 is assigned to foo state
run_logic_that_works_with_state_of_foo() // fooValue1 is used in the logic
> setFooState(fooValue2) // a cascaded algorithm within "runLogic" leads to some logic that prepares foo state (again)
> run_logic_that_works_with_state_of_foo() // fooValue2 is used in the logic
> clearFooState() // no we clear fooValue2 from the foo state. it is now null, but not fooValue1 like before
... !! // at this moment - we are still somewhere within the initial call to run_logic - we have lost our fooValue1 state!
clearFooState() // now we think we clear fooValue1 from the foo state. but it was already null
```

But of course we can fix this, right? We have solved problems of this kind already a lot and we are all experts in
problem solving. To make it more interesting we now introduce also a 2nd state that we want to make revertable in
addition to our foo state. And we make it so that the fooState is applied conditionally...

### try 3: example without threadly-streaming, but also having solved flaw #1 & #2

```java
var oldFooState = getFooState();
var oldBarState = getBarState();
if(conditionIsSatisfied){

setFooState(fooValue1);
}

setBarState(barValue1);
try{

run_logic_that_works_with_state_of_foo_and_bar();
}finally{

setBarState(oldBarState);
if(conditionIsSatisfied){

setFooState(oldFooState);
}
}
```

**You might already expect it: This approach - despite all the increased clutter that we had to produce already - still
hides some severe robustness flaws from us:**

What would happen if any exception occurs during the execution of `setBarState()`? In such a case our finally logic
would not apply and we have an uncaught exception leaving a dirty fooState to our thread. So we are again left alone
with flaw No.1. Of course we could adhoc fix this by moving the `setBarState()` invocation into the try section to solve
our "transactional" problem. An ugly - but admitted truly robust - solution would then look like this:

### try 3: example without threadly-streaming and having solved all findings

```java
var oldFooState = getFooState();
if(conditionIsSatisfied){

setFooState(fooValue1);
}
try{
var oldBarState = getBarState();

setBarState(barValue1);
try{

run_logic_that_works_with_state_of_foo_and_bar();
}finally{

setBarState(oldBarState);
}
}finally{
if(conditionIsSatisfied){

setFooState(oldFooState);
}
}
```

In real case scenarios it often becomes even more complex than what we have seen here in our example above. Normally the
solution is either to
drop the requirement of bullet-proof code altogether and simply accept the "negligible" risk that unexpected things
might happen. Or you live with the fact that your whole codebase is cluttered with nested try/finally statements all
over the place.

**Or you use _threadly-streaming_ that gives you all the robustness from the code before - without the clutter**:

```java
var revert = DefaultStateRevert.chain(chain -> {
if (conditionIsSatisfied) {
chain.append(pushFooState(fooValue1));
}
chain.append(pushBarState(barValue1));
});
try{

run_logic_that_works_with_state_of_foo_and_bar();
}finally{
revert.

revert();
}
```

- We also changed the semantics of the encapsulated state transitions. Our helper methods are not called
`setFooState()` anymore but rather `pushFooState()`. Same for the barState.

- The code example above is 100% transactionally consistent. If an error or exception of any kind happens during the
execution of `pushFooState()`, `pushBarState()` or the normal business logic in
`run_logic_that_works_with_state_of_foo_and_bar()` the overall termination and cleanup logic will recover to the state
of foo & bar from the very initial state. Even the partial creation of the lamda structure within the `chain()` method
is properly reverted behind the scenes.

- It is also reentrant-capable due to the stack nature with _push*_ instead of _set*_ and due to the fact that we store
the revert-closure (the result from the `chain()` invocation) on the thread stack it is immutable & thread-safe as
well.

- In fact the managed logic is even more robust than even the best common example from above because it will only
memorize & apply the
revert logic for the `fooState` if the `conditionIsSatisfied` condition was really applicable at a single point in
time

- Note the subtle difference in our nested try/finally above we call `conditionIsSatisfied` 2 times and even if it
resolves to _false_ we have allocated the `oldFooState` on our stack unnecessarily. If the fooState creation itself
was a
non-trivial or lazy task we have initialized a complex object on our stack without any good reason. But this is also
solved with the _Chain-API_ from _threadly-streaming_.

Try it out!

---

# <a name="parallel-streaming-api"></a>Parallel Streaming API

TODO
Loading

0 comments on commit 4c8dcb1

Please sign in to comment.