spring-projects / spring-webflow

Spring Web Flow
https://spring.io/projects/spring-webflow
Apache License 2.0
324 stars 231 forks source link

Add a new Java-based Flow Definition syntax [SWF-295] #157

Open spring-operator opened 17 years ago

spring-operator commented 17 years ago

Keith Donald opened SWF-295 and commented

There are several opportunities for defining simpler flow definitions in native Java. This ticket should realize some of these opportunities.

Specifically:

Example Java-based flow:

@Flow("editAccount") public class EditAccountFlow {

@PersistenceContext(type=PersistenceContextType.EXTENDED)
private EntityManager entityManager;

private Long id;

private Account account;

// flow startup callback (typed flow input)
public State start(Long id) {
    this.id = id;
    this.account = entityManager.find(Account.class, id);
    // return the next view
    return new AccountView();
}

@ViewState
public class AccountView {
    // view event handler
   @Transactional
   public State confirmEdits() {
       entityManager.joinTransaction();
       // end flow
      return new Finish();
   }
}

// flow end callback
public void end() {
    this.entityManager.close();
}

}


Affects: 1.0

Attachments:

15 votes, 19 watchers

spring-operator commented 17 years ago

Hideyuki Suzumi commented

Is the Flow class that is not Serializable possible? Many DAOs are not Serializable.

spring-operator commented 16 years ago

Hideyuki Suzumi commented

I think it is better to use constants instead of inner classes.

Example:

@Flow @Component @Scope("request") // scope is free public class FooFlow {

// view-state
@View("/foo/foo.jsp")
public static final String FOO_VIEW = "FOO_VIEW";

// subflow-state
@Subflow("barFlow")
// subflow's transitions
@Transitions(
    @Transition(on=BarFlow.OK, to="barFlowOK"),
    @Transition(on=BarFlow.CANCEL, to=FOO_VIEW)
}
public static final String BAR_FLOW = "BAR_FLOW";

// end-state
@End
public static final String END = "END";

...

@Autowired
private FooData fooData;

@Autowired
private FooDao fooDao;

...

// this flow's input-mapper
@FlowInput
public void setInput1(Long in1) {
    ...
}

@FlowInput
public void setInput2(String in2) {
    ...
}

// this flow's output-mapper
@FlowOutput
public FooFlowOutput getOutput() {
    ...
}

// subflow's input-mapper
@SubflowInput(subflow=BAR_FLOW, name="param")
public String getBarFlowParam() {
    ...
}

// subflow's output-mapper
@SubflowOutput(subflow=BAR_FLOW, name="result")
public void setBarFlowResult(Boolean result) {
    ...
}

...

// start-state
@Start
// action-state
@Action
public String start() {
    ...
    return FOO_VIEW; // return the next view
}

...

@Action
public String barFlowOK() {
    ...
    return END; // end flow
}

...

// flow end callback
@EndAction
public void end() {
    ...
}

}

FooFlow, FooData and FooDao need not be Serializable.

spring-operator commented 16 years ago

Joseph commented

i also prefer not to use inner classes

spring-operator commented 16 years ago

Keith Garry Boyce commented

Is it also possible to support this in JDK 1.4 using conventions rather than annotations

spring-operator commented 14 years ago

!!Use pwebb rather than philw commented

Looking at the initial cut of the code I was wondering if perhaps transition methods should return Class<? extends State> rather than a state instance. I am specifically thinking about what might happen when you need to start to serialize the state into HTTP session when the flow is paused. Making each state serializable is one option but you then may run into issues if you want a state to be injected with a singleton bean.

You could even make Class<? extends State> an attribute of @Transition, this should help guide the user a bit as they have IDE code-assist. Also having the option in the annotation means that you could do away with the start() method and just use a standard constructor annotated with @Transition (in this context @Transition being more akin to start-state in webflow 2).

Eg:

@Flow public class BookingFlow {

private Booking booking;

@Transition(to=EnteringBookingDetails.class)
public BookingFlow(@EventParam("hotelId") Long hotelId, Principal currentUser) {
    booking = Booking.newBooking(hotelId, currentUser);
}

class EnteringBookingDetails extends ViewState {
    @Transition(to=ReviewingBooking.class)
    public void next() {
    }
}

class ReviewingBooking extends ViewState {
    @Transition(to=Finished.class)
    public void confirm() {
        booking.persist();
    }
}

class Finished extends EndState {}

}

spring-operator commented 14 years ago

Keith Donald commented

Phil, How could you see handling dynamic transitions with your approach? The main reason methods are transitions is to allow for dynamic logic/branching using plain Java code (removing the need for action or decision state types with this model).

I'm wondering if serialization of state instances will be an issue in practice, given Spring 3's support for serializable scoped proxies (even supported for singleton beans).

spring-operator commented 14 years ago

!!Use pwebb rather than philw commented

Hi Keith,

I was thinking thinking:

@Transition public Class<? extends State> next() { if(someThing()) { return Here.class; } return There.class. }

Or I guess you could use the same model that you have and just store the class of the object returned rather than the instance.

I was not aware of the new Spring 3 serialization proxy support, so that probably provides a clean way to deal with it. Also, thinking about it some more you can't just use an annotation on the flow constructor to indicate a start state as you have no way to re-create the flow when it is resumed.

Still, something does not feel entirely right about needing to have all Flows and States serializable but I can't quite work out why, just a gut instinct. States and Flows feel closer to singletons than objects that you would want in session. I find serialization exceptions from objects in flow/viewScope seem to come up quite often so I guess another area where you need to think about this worries me.

If the @Flow lifecycle is changed so that webflow is responsible for serializing only the objects that end up in flow/viewScope on pause and re-injecting them on resume this may help. Eg:

@Flow public class SomeFlow implements Serializable {

@Autowired @Scope(proxyMode=ScopedProxyMode.INTERFACES) private SomeServiceBean service;

@Autowired @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) private Some3rdPartyServiceWithoutInterface service2; //NOTE: now you need cglib

//A flowScope varaible private Booking booking; ... }

Becomes:

@Flow public class SomeFlow {

@Autowired private SomeServiceBean service;

@Autowired private Some3rdPartyServiceWithoutInterface service2;

@FlowScope private Booking booking; //Only this is serialized ... }

PS. Is there any reason why JIRA is not emailing me?

spring-operator commented 14 years ago

Keith Donald commented

Phil,

Flow and State definitions are singletons, I agree. Flow executions (or flow instances), however, are stateful objects, generally stored in the user session and scoped by a conversation boundary. Apply this to the Java-based flow programming model, and the Flow and State classes are definition constructs--singleton FlowDefinition/StateDefinition Web Flow objects are actually compiled from them. But @Flow and State instances are stateful, and correspond to the FlowExecution/FlowSession Web Flow engine counterparts. To this end, they feel very much like session scoped beans to me, where instance state is implicitly session scoped (flow scoped in this case). Semantically this feels quite natural to me, and yes it does carry the serialization requirement, the same requirement a typical session-scoped bean would have. I'm interested to see if this will raise issues in practice... we should know real soon.

spring-operator commented 14 years ago

!!Use pwebb rather than philw commented

It probably wont be an issue if the @Scope option sorts it, until the serialization code is needed it is probably a bit early to know. I see what you are saying about flow executions being instances, and I completely agree that having instance state as flowScope is very natural. I had just envisaged that the flow resume would create a new instance and then use reflection to set the instance state from the flowScope in session.

One other really small thing, in fact this is so dumb I almost feel stupid saying it. Would it be worth renaming the classes in the engine package so that they are different to the .java package? I can see a lot of people accidentally importing from the wrong package.