如何使用事务/原子进行多个REST请求?

时间:2015-11-11 15:24:46

标签: java spring rest transactions rollback

我有以下情况 我有一个REST客户端,作为其他3个REST客户端的外观。 (我正在用Java编程,使用Spring Boot)

客户的责任之一包括对用户进行CRUD操作 现在,暴露自己的REST API的所有其他3个系统都有某种用户管理。

当我收到创建用户的请求时,我必须通过他们的REST API在这3个系统上创建它们并保留在我的数据库中。

现在,在最好的情况下,我只是调用他们的API,将用户插入我的数据库中,一切都很棒。

但是,请考虑用户创建仅在1个外部服务上成功的情况。我是否会重试所有其他操作?我是否尝试删除用户已被取代的用户?

这样做的正确方法是什么?

3 个答案:

答案 0 :(得分:3)

没有一种简单的方法可以做到这一点。如果“事务”的任何部分失败,则无法可靠地回滚或重试以保证所有系统的一致性。您需要与所有三个(四个系统)紧密集成才能使用分布式事务系统。

答案 1 :(得分:2)

一种方法(假设您可以容忍节点之间的不同状态):

  1. 让我们说,你的门面有持续的传入CRUD请求队列。一旦新请求<p:dataTable value="#{mainBean.liste}" styleClass="tabelle" var="daten" editable="true" rows="6" emptyMessage="Keine Daten bisher Erfasst" widgetVar="erfasstTabelle" paginator="true" resizableColumns="true" paginatorPosition="bottom" id="tabelleUebersicht" paginatorTemplate=" {CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown} Direkt zu Seite: {JumpToPageDropdown}" currentPageReportTemplate="Seite: {currentPage} von {totalPages}" filteredValue="#{mainBean.gefiltert}"> <p:ajax event="rowEdit" listener="#{mainBean.onRowEdit}" update=":myForm:msgs :myForm:tabelleUebersicht" /> <p:ajax event="rowEditCancel" listener="#{mainBean.onRowCancel}" update=":myForm:msgs :myForm:tabelleUebersicht" /> <f:facet name="header"> <p:outputPanel> <h:outputText value="Alle Felder durchsuchen" /> <p:inputText id="globalFilter" onkeyup="PF('erfasstTabelle').filter()" style="width:150px" placeholder="Suchbegriff" /> </p:outputPanel> </f:facet> <p:column headerText="AVS ID" filterMatchMode="startsWith" filterBy="#{daten.nummer}"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{daten.nummer}" /> </f:facet> <f:facet name="input"> <p:inputText id="nummerIn" value="#{daten.nummer}" style="width:100%" /> </f:facet> </p:cellEditor> </p:column> <p:column headerText="Rechtsform" filterMatchMode="startsWith" filterBy="#{daten.rechtsform}"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{daten.rechtsform}" /> </f:facet> <f:facet name="input"> <p:inputText id="rechtsfIn" value="#{daten.rechtsform}" style="width:100%" /> </f:facet> </p:cellEditor> </p:column> <p:column headerText="Rechtsf.Speziell"> <p:cellEditor> <f:facet name="output"><h:outputText value="#{daten.rechtSpeziell}" /></f:facet> <f:facet name="input"> <p:selectOneMenu value="#{daten.rechtSpeziell}" style="width:100%"> <f:selectItems value="#{mainBean.rechteGenau}" var="r" itemLabel="#{r}" itemValue="#{r}"/> </p:selectOneMenu> </f:facet> </p:cellEditor> </p:column> <p:column headerText="Erfasser" filterMatchMode="startsWith" filterBy="#{daten.erfasser}"> <p:cellEditor> <f:facet name="output"><h:outputText value="#{daten.erfasser}" /></f:facet> <f:facet name="input"> <p:selectOneMenu value="#{daten.erfasser}" style="width:100%"> <f:selectItems value="#{mainBean.erfassers}" var="e" itemLabel="#{e}" itemValue="#{e}"/> </p:selectOneMenu> </f:facet> </p:cellEditor> </p:column> <p:column headerText="Datum"> <p:cellEditor> <f:facet name="output"> <h:outputText value="#{daten.datum}" /> </f:facet> <f:facet name="input"> <p:inputText id="datumIn" value="#{daten.datum}" style="width:100%" /> </f:facet> </p:cellEditor> </p:column> <p:column style="width:32px"> <p:commandLink value="löschen" styleClass="ui-icon ui-icon-trash" /> </p:column> <p:column style="width:32px"> <p:rowEditor /> </p:column> </p:dataTable> 在队列中,您就开始要求REST客户端执行它;
  2. 一旦所有REST客户端执行请求并报告成功,您就可以将其从队列中删除,并使此更改对系统的全局状态有效。例如,如果@ManagedBean @ViewScoped public class MainBean implements Serializable{ private static final long serialVersionUID = 1L; private ErfasstDAO erfasstDAO; private List<Erfasst> liste; private List<Erfasst> gefiltert; private List<String> erfasser; private boolean neu; private List<String> rechtSpeziell; private List<String> rechteGenau; //Neuanlage private Long nummer = null; private FindeId findeId; private String recht; private List<String> rechtsformen; private String rechtDetail = ""; private Date datum; private String notiz = ""; private List<String> erfassers; @PostConstruct private void init(){ findeId = new FindeId(); erfasstDAO = new ErfasstDAO(); erfasser = new ArrayList<>(); erfasser.add("Benjamin"); erfasser.add("Uwe"); erfasser.add("Rolf"); liste = erfasstDAO.alleEintraege(); rechteGenau = new ArrayList<String>(); rechteGenau.add(" "); rechteGenau.add("Vollständig"); rechteGenau.add("Unterlagen gezogen"); rechteGenau.add("Identifizierung/-wB-Ermittlung durch CP"); rechtsformen = new ArrayList<>(); rechtsformen.add("natürliche Person"); rechtsformen.add("juristische Person"); erfassers = new ArrayList<>(); erfassers.add("Benjamin"); erfassers.add("Uwe"); erfassers.add("Rolf"); } public void setRechteGenau(List<String> rechteGenau) { this.rechteGenau = rechteGenau; } public void setErfassers(List<String> erfassers) { this.erfassers = erfassers; } public void inputListener(AjaxBehaviorEvent event) { if (event != null) { UIInput input = (UIInput) event.getComponent(); Long id = (Long) input.getValue(); if (id != 0) { if (findeId.finden(id) != null) { FacesMessage m = new FacesMessage( FacesMessage.SEVERITY_ERROR, "Vorhanden", findeId.finden(id) + " bereits vorhanden"); FacesContext.getCurrentInstance().addMessage(null, m); } else { FacesMessage m = new FacesMessage( FacesMessage.SEVERITY_INFO, "Noch frei", id + " noch nicht vergeben!"); FacesContext.getCurrentInstance().addMessage(null, m); } } } } public void speichern() { Erfasst er = new Erfasst(); er.setNummer(nummer); er.setErfasser("Benjamin"); er.setRechtsform(recht); er.setDatum(datum); er.setRechtSpeziell(rechtDetail); er.setNotiz(notiz); if(erfasstDAO.neu(er)) { FacesMessage m = new FacesMessage( FacesMessage.SEVERITY_INFO, "Angelegt", String.valueOf(nummer)); FacesContext.getCurrentInstance().addMessage(null, m); } liste = erfasstDAO.alleEintraege(); } public void onRowEdit(RowEditEvent event) { if(erfasstDAO.abgeaendert((Erfasst) event.getObject())) { FacesMessage msg = new FacesMessage("Geändert", String.valueOf(((Erfasst) event.getObject()).getNummer())); FacesContext.getCurrentInstance().addMessage(null, msg); } else { FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Fehler", "Beim ändern von: " + String.valueOf(((Erfasst) event.getObject()). getNummer() + " ist ein Fehler aufgetreten")); FacesContext.getCurrentInstance().addMessage(null, msg); } liste = erfasstDAO.alleEintraege(); } public void onRowCancel(RowEditEvent event) { FacesMessage msg = new FacesMessage("Bearbeitung abgebrochen", String.valueOf(((Erfasst) event.getObject()).getNummer())); FacesContext.getCurrentInstance().addMessage(null, msg); } public boolean isNeu() { return neu; } public void setNeu(boolean neu) { this.neu = neu; } public List<String> getRechtSpeziell() { return rechtSpeziell; } public void setRechtSpeziell(List<String> rechtSpeziell) { this.rechtSpeziell = rechtSpeziell; } public Long getNummer() { return nummer; } public void setNummer(Long nummer) { this.nummer = nummer; } public String getRecht() { return recht; } public void setRecht(String recht) { this.recht = recht; } public List<String> getRechtsformen() { return rechtsformen; } public void setRechtsformen(List<String> rechtsformen) { this.rechtsformen = rechtsformen; } public String getRechtDetail() { return rechtDetail; } public void setRechtDetail(String rechtDetail) { this.rechtDetail = rechtDetail; } public Date getDatum() { return datum; } public void setDatum(Date datum) { this.datum = datum; } public String getNotiz() { return notiz; } public void setNotiz(String notiz) { this.notiz = notiz; } public List<String> getRechteGenau() { return rechteGenau; } public List<String> getErfassers() { return erfassers; } public List<Erfasst> getListe() { return liste; } public List<Erfasst> getGefiltert() { return gefiltert; } public void setGefiltert(List<Erfasst> gefiltert) { this.gefiltert = gefiltert; } } 是CREATE,那么新用户对外部世界是可见的,如果req更新,则更新对外部世界可见,依此类推。这意味着全局状态是门面系统数据库中存储的内容;
  3. 现在,如果您的外观在req正在进行中时(如果所有REST客户端都报告成功),该怎么办?重新启动后,您的外观必须从(持久)队列中获取所有挂起的请求,并将它们推送到REST客户端。这意味着REST客户端可以检测它们是否已经处理了该特定请求(并且它刚刚发生,在它发生故障之前没有处理回复)。通常,这是通过使用唯一的请求ID来实现的,例如,UUID。
  4. 如果您的系统只有部分REST客户端处理请求(这意味着数据只能通过外观访问外部世界),则上述过程有效。如果没有,您需要一些支持分布式事务的系统(谷歌用于两阶段提交)。

答案 2 :(得分:1)

您需要根据具体情况处理它。在您提供的示例中,您可以尝试删除,但这也可能会失败。

一旦出现故障,您需要:

  1. 处理创建用户的重试
  2. 处理可能访问用户的客户端,即使只创建了3个中的一个
  3. 对于重试,您可以让发起人重试或排队请求。

    在这两种情况下,您可能都希望设计api,以便在尝试重新创建已创建的用户时,它会将其视为更新。

    例如,三个中只有一个成功。

    启动请求的网页会返回错误。用户重试。这次你更新第一个并重试创建第二个和第三个。

    对于客户端查找记录并获取部分用户,您需要第4个记录系统来跟踪创建的记录,或者客户端本身只需要查看1个三个被创造了。如果您的客户总是只是一次只看三个中的一个,这甚至可能不是问题。