bertilmuth / requirementsascode Goto Github PK
View Code? Open in Web Editor NEWBehavior driven service development.
License: Apache License 2.0
Behavior driven service development.
License: Apache License 2.0
Currently, calling ModelRunner.restart()
only resets internal variables to their initial state.
When calling ModelRunner.stop()
first, and then restart()
, the ModelRunner
is not running.
That is confusing.
Instead, restart should really rerun the model.
Currently, ModelRunner.reactTo()
returns the latest step that has been run. This information is the same as calling ModelRunner.getLatestStep()
, so it's redundant and only there for historical reasons.
It would be much more helpful if ModelRunner.reactTo()
returned the latest event that has been published as a consquence of the call. To cover the case that none was published, it should return Optional<Object>
.
Currently, it is not possible to leave out the event class in the simple event handling case,
and react as soon as a condition is fulfilled (in contrast to the use case models, where this is already possible).
So the request is to implement reacting in the .when
only case.
Are you using requirementsascode beyond the examples? If not, what's keeping you from it?
If there is one thing you would improve, what would that be?
For feedback, or a chat with other users and myself, join the Gitter community:
Requirements as Code on Gitter
In most situations, specifying a system reaction as Runnable or reference to a method without arguments is the way to go. This was the breaking change introduced in 0.9.0.
However, examples have shown that in some circumstances, having a model runner as a system reaction's argument can be helpful.
So this should be available optionally.
The ModelRunner
has a method canReactTo(eventClass)
that checks whether the model runner can currently react to the events of the specified eventClass
.
What is missing is the possibility to query all the event classes that the runner can currently react to.
The solution should introduce a new method getReactToTypes
that returns those event classes.
At the moment, when a system reaction method throws an exception that is not
handled somewhere in the model, the ModelRunner
throws an UnhandledException
, wrapping the thrown exception.
That can be confusing for users.,
So the solution is: unhandled exceptions will be rethrown without modification.
Bug description:
Actual: When there are 2 steps that can react, in two flows that have NO condition specified, and the steps react to the same event, the UseCaseModelRunner throws a StackOverflowException.
Expected: the UseCaseModelRunner throws a MoreThanOneStepCanReact exception.
Steps to reproduce:
Create/run a use case model similar to the following:
UseCaseModel useCaseModel = useCaseModelBuilder
.useCase("Use Case")
.basicFlow()
.step("Step 1").user(String.class).system(s -> System.out.println(s))
.useCase("Another Use Case")
.basicFlow()
.step("Step 2 with same event as Step 1").user(String.class).system(s -> System.out.println(s))
.build();
useCaseModelRunner.run(useCaseModel);
useCaseModelRunner.reactTo(new String("Some text"));
Requirementsascode version: 0.4.0
Operation system: Windows 10/Eclipse, but should occur in any environment
The hexagon example is a bit outdated. The idea is to update it to new insights, to turn it into a lightweight example of an architecture that has a application / use case layer.
Right now, requirementsascode does not allow to include other use cases from a use case.
Including use cases makes it possible to reuse steps from other use cases.
It avoids error prone duplication of steps between use cases.
R1. The solution shall allow to include whole use cases, with all of their flows. For this, a new .includeUseCase statement shall be created.
R2. Including shall be possible from any place in a flow where a step definition is possible.
R3a. When the runner reaches an .includeUseCase statement, the runner shall continue with the first step of the basic flow of the included flow, with the exception that an alternative flow of the included use case may interrupt.
R3b. When the last step of an included flow has been run, the runner shall continue with the next step of the including flow (after the .includeUseCase statement).
According to the Javadoc comment for ModelRunner.canReactTo()
, the method must return true if its argument matches an event class in the model, or a sub class of it.
Right now, it only works if the exact event class matches. Not for subclasses.
The current behavior is inconsistent with ModelRunner.reactTo()
, and needs to change to match the Javadoc comment.
... because FlowPosition is not serializable.
It should be.
Until the v0.5 releases, in order to use a template for requirementsascodeextract, you needed to include another template (extract.ftl).
This has proven to be tedious. It will no longer be necessary once this issue has been resolved.
Right now, on every call to reactTo
, all steps are checked whether the actor matches the run actor. Instead, this filtering operation can be done internally on calling as
and run
, and the results can be cached.
Currently, the ModelRunner.reactTo()
method either accepts a single event, or mutliple events as varargs (comma-separated).
When, by accident, the user of requirementsascode uses a list as parameter, this will be treated as a single event, rather than a list of events.
So the request is: change this behavior, treat a list passed to ModelRunner.reactTo()
as a list of events.
The ModelBuilder allows certain configurations that are superfluous.
For example, it allows to specify more than one when
conditions, but starting with the second one, they get overwritten.
The solution should eliminate these cases.
Right now, if you want to specify an actor for a use case, you need to specify it for each step.
Like so:
Model model =
modelBuilder.useCase(USE_CASE).basicFlow()
.step(CUSTOMER_ENTERS_TEXT).as(customer).user(EntersText.class).system(displaysEnteredText())
.step(CUSTOMER_ENTERS_TEXT_AGAIN).as(customer).user(EntersText.class).system(displaysEnteredText())
.build();
It would be nice to be able to specify a default actor for a whole use case. It will be used for every step that includes a .user()
part. Like so:
Model model =
modelBuilder.useCase(USE_CASE).as(customer).basicFlow()
.step(CUSTOMER_ENTERS_TEXT).user(EntersText.class).system(displaysEnteredText())
.step(CUSTOMER_ENTERS_TEXT_AGAIN).user(EntersText.class).system(displaysEnteredText())
.build();
As you can see, the default actor needs to be specified after the .useCase()
part, right before the .basicFlow()
.
If you create a model without use cases and run in through testextract.ftl, a NullPointerException is thrown.
This will be fixed, and a separate template (testextract_flowless.ftl) will be provided.
Right now, a system reaction method specified by .system
can only consume events.
While publishing events is possible through injecting a dependency (to an event publisher) into the system reaction method, there is no support for explicitly modeling event publishing yet.
The solution requested is: create a new method .submitPublish
in the model builder that doesn't only consume events, but can also return events.
The model runner takes the returned events and sends them to its own .reactTo
method. This default behavior can be overriden by specifying a custom event handler with .publishWith
.
Right now, requirements as code only supports synchronous processing, and it is not thread safe.
For asynchronous processing, implement a simple event queue and provide a usage example in the README.md.
Because of issue #13, handling of "unhandled" events will be supported.
So there will be the need to adapt the system reaction for 2 scenarios:
for the "handled" events case, and for the "unhandled" event case.
Goal:
Rename modelRunner's adaptSystemReaction
to handleWith
,
and introduce the new method as handleUnhandledWith
.
Rename SystemReactionTrigger
to StandardEventHandler
.
Oracle has ended public updates for Java 8 for Commercial Users:
https://www.oracle.com/technetwork/java/java-se-support-roadmap.html
Time to migrate to Java 11. As OpenJDK 11 does no longer include JavaFX, this will need to be included as separate modules.
Requirements as code core and Requirements as code extract will still be compiled against Java 8 to maximize compatibility.
Recording steps in a TestModelRunner
is nice, but what if you need to record steps in production? It would be more clear of you could use the same ModelRunner
for everything.
So the methods of the TestModelRunner
should be integrated to the standard ModelRunner
.
Additionally, methods startRecording()
and stopRecording()
should be added to allow for fine grained control of when to record.
Up to v0.7.2, events that the runner does not react to are silently consumed.
Goal: implement a handler for "unhandled" events.
Currently, the getRunStepNames
method of the TestModelRunner
returns a string of the step names, joined by semicolons.
Asserting this string is error prone (but good for detailed comparison).
So there will be another method, hasRun
, returning a boolean, with string array input, to check whether specified steps have been run.
The when
method to specify a precondition of a step is not properly named.
The name caused users to think that the step is only executed at a distinct point in time (like an event).
But the truth is: the step is executed while the condition holds.
To clarify that, when
should be renamed to condition
.
Note that the API should stay stable in the v1.x versions after that change.
Up to v0.6.0, it was only possible to build flows in one go, using the UseCaseModelBuilder.
Starting with v0.6.1, it will be possible to build flows incrementally.
So the following two examples are equivalent:
Example 1:
useCaseModelBuilder.useCase("Use Case 1").basicFlow()
.step("1").system(reactsToStep1())
.step("2").system(reactsToStep2());
Example 2:
useCaseModelBuilder.useCase("Use Case 1").basicFlow()
.step("1").system(reactsToStep1());
useCaseModelBuilder.useCase("Use Case 1").basicFlow()
.step("2").system(reactsToStep2());
Up until requirements as code v1.0.0, the focus was on getting a stable API definition, not performance.
The ModelRunner
class creates several collections for each call to reactTo()
, leading to a lot of garbage collection. That's why in my personal tests, the throughput was limited to around 800k events per second.
By reducing the amount of garbage collection, the goal is to improve throughput to at least a few million events per second.
Dear downloaders of requirements as code.
I can see in the statistics every month that there are still downloads of the "old versions" of requirements as code. While, of course, you have every right to do so, I am curious why you don't use the later versions.
Please let me know in the comments, or write me an email to: [email protected]
Thank you.
Bertil
Since Java 12 has reached GA, the compatibility must be checked.
This is done by adapting the Travis CI configuration.
Accidentally, in example HelloWorld04
, the user needs to enter her age before being prompted to do so.
The problem is caused by this line:
modelRunner.reactTo(entersText(), entersText());
The reason for that is that both entersText()
method calls are evaluated before reactTo()
is called,
and entersText()
is where user input happens, so both inputs are done at once.
This can be fixed easily by making to disting reactTo()
calls.
So far, steps outside of a flow can only contain an .on(..)
statement for defining the types of events to process.
So the proposal is: enable explicitly defining user commands with a .user(...)
statement, analogous to the steps of a flow.
When implementing issue #31, the reactTo()
methods will return inconsistent values if not changed.
So the request is: always return the step run latest by the model runner -
same as calling ModelRunner.getLatest()
The StandardEventHandler
class name is misleading - an instance of it contains information about the step to be run. So it should be renamed to StepToBeRun
, and include a run()
method that triggers the system reaction.
So far, it is also cumbersome to find out details about the step to be run, in a custom event handler method (as defined with modelRunner.handleWith()
).
In the future, the StepToBeRun
class should instead have the following methods:
getStepName()
that returns a String
(the name of the step to be executed).getCondition()
that returns an Optional<? extends Condition>
(the precondition of the step to be executed, or Optional.empty()
if the user hasn't defined a precondition).getEvent()
that returns an Optional<? extends Object>
(the event of the step to be executed, or Optional.empty()
if the user hasn't defined a event).getSystemReaction()
that returns an Object (the object representing the system reaction of the step to be executed.When publishing events is enabled (see #43), there is no need anymore for the "include use case" feature.
Rather, chaining models where the higher level model publishes events to the lower level model will be the preferred way. How this is done is documented in the Included Use Case test case.
All code relating to the "include use case" feature will be discarded. This is not considered a breaking change, since the "include use case" feature has previously been undocumented. Please leave a comment if this will cause problems in your implementation.
For simple use cases, e.g. simple CRUD use cases, the syntax up to v0.6.x is not very concise.
The solution will provide addititional syntax that doesn't use flows or steps,
but just handling of events without sequence.
So for a simple hello world example, the code might read:
Model.builder()
.handles(UserNameEntered.class).with(displayHelloUser())
.build();
A use case may be specified, but if it is not, a default use case ("Handle events") is assumed.
On top of that, the solution should support when conditions.
Currently, the handleUnhandledWith()
method only handles regular events that the model runner can't react to.
As exceptions are treated as events as well, the handleUnhandledWith()
method should handle exceptions as well.
Explore whether event driven systems like Apache Kafka or Akka Actors play together well with requirements as code, or what needs to be changed to make them play nicely together.
Currently, the run
method returns void. That makes it impossible to chain with the reactTo
method.
So the request is to make the run
method return ModelBuilder.
The UseCaseModelBuilder
class will be instantiated differently, starting with v0.7.0.
Also, the class will be called ModelBuilder
.
Instead of writing:
UseCaseModelBuilder.newBuilder();
You will write:
Model.builder();
Currently, it is only possible to overwrite the default event handling using the modelRunner.handleWith()
method.
It is currently not possible to adapt the behavior of the TestModelRunner
WITHOUT destroying its step tracking behavior (because that's implemented using modelRunner.handleWith()
as well).
A fix is needed, to enable adapting the TestModelRunner
's event handling behavior while retaining the step tracking behavior.
While it is consistent with the internal implementation, having to specify the ModelRunner
as part of conditions, reactWhile and autonomous system reaction methods is confusing to users.
So the future conditions will be specified by users by a functional interface similar to Callable<Boolean>
, while the future autonomous system reaction methods will be specified as Runnable
.
Currently, if you create a simple event handling model (not a use case model),
you can create at most one use case.
This should be changed to enable creating multiple use cases.
Right now, systemPublish()
is not available if only a condition, but no event is specified, and you're outside of a flow.
So the proposal is: implement systemPublish()
in the above case.
Currently, when a step has a condition that always evaluates to true
, the step is reentered infinitely. This leads to a StackOverflowError.
Instead, a more telling exception (InfiniteRepetition
) should be thrown, indicating the step where the infinite repetition occured.
Hi Bertil,
Here are my remarks about your prototype (https://github.com/bertilmuth/requirementsascode/tree/master/requirementsascodeexamples/hexagon/src/main/java/hexagon).
First, your left-side Adapter (i.e. ConsoleAdapter) must not reference the other right-side Adapters (i.e. WriterAdapter and RepositoryAdapter). The Hexagonal Architecture pattern describe a situation where:
Regarding your state management. You seem to delegate the business-logic state to your use case{Model|ModelBuilder|...} framework. I didn't have time to study the whole thing (with Steps, Flows, Actors, etc) but I'm wondering how you will handle the per-user state-machine.
Anyway. For me the situation here with that UC framework goes a little way out our the Hexagonal Architecture pattern and I would suggest to let your use case "controller" out of the hexagon and making it only deal with the left-side Adapters you have.
Now, to answer your initial question: where to put state dependent behavior? Say user gets happy poem first time she asks. 2nd time sad one. 3rd time another one , I would suggest something like that:
Of course this is only one way to implement it (among many), you can also delegate the UsersSessions/state management to an external system for instance (to make it persistant even after a crash of your service). In that case, you will have to add a new right-side port/adapter for your hexagon business logic to be able to use it.
Hope it will clarify what we already discussed on twitter. Happy coding !
Thomas
Currently, the syntax for simple event processing is:
when(condition).handles(eventClass).with(systemReaction)
The problem with this naming convention will start to occur once it will be possible
to leave out .handles
and only define a .when
condition:
when(condition).with(systemReaction)
That isn't understandable. Apart from that, it is also inconsistent with how the
use case models are defined.
So the proposal is as described in the issue name. The new form would look like:
when(condition).on(eventClass).system(systemReaction)
or
when(condition).system(systemReaction)
or
on(eventClass).system(systemReaction)
Also, in the use case model, .handles
will be renamed to .on
as well.
Experiments with the current methods of TestModelRunner have shown they are not really serving their purpose well. The getRunStepNames
method returns a colon separated list that is too easy to get wrong in the test cases. The hasRun
method is good concerning its check, but only returns a boolean value. That value says nothing about the steps that have actually been run.
So replace the old method with two new methods:
getRecordedStepNames
getRecordedEvents
Those methods can be used for easy comparison of the expected vs. actual step names/events that caused a system reaction (they provide the actual, of course).
They both return an array, for easy comparison with JUnits assertArrayEquals
method.
The resolution of this issue will change the verbs in the API to third person singular.
So far, it was in the imperative form.
So instead of continueAfter
, the new verb will read continuesAfter
.
This will provide a more direct translation from use cases to text,
make the models more readable and simplify internal processing.
Due to the recent developments, the serialization of the ModelRunner doesn't work any more. That affects the Akka example, that needs to be adapted.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.