We have a project that uses external travel system and allows to buy tickets. We keep ticket info locally but actual state is defined in external system:
FirstCorpService srv1 = new FirstCorpService();
FirstCorpTicket tkt1 = srv1.buyTicket(...);
tktEntity1 = ticketRepository.save(CorpCode.CORP1, tkt1.getId(), Status.NEW);
...
srv1.applyDiscount(tkt1);
...
tktEntity1 = ticketRepository.findById(id);
FirstCorpTicket tkt1 = srv1.loadTicket(tktEntity1.getExternalId());
if ( ! tkt1.isCancelled()) {
srv1.cancel(tkt1);
tktEntity1.setStatus(State.CANCELLED);
ticketRepository.save(tktEntity1);
}
Now we have another 2 ticket systems with same domain, object model and minor difference in operation flow.
We need to abstract away concrete types FirstCorpService
and FirstCorpTicket
.
It is important to abstract away both service and data simultaneously. Concrete service can't operate on generic data (ticket). To apply discount or cancel ticket you need details from actual reservation system which vary across providers.
Are there design patterns that solve provided design problem when both state and algorithm should correspond each other?
TL;DR To keep implementation provider-agnostic I think about using polymorphism on object that wraps both service and data. Probably I violate SOLID / GRASP in following implementation design, that's why I ask for help.
Abstract away ticket operations:
interface TicketI {
long getId();
void applyDiscount();
boolean isCancelled();
void cancel();
}
As typical OOP language doesn't support polymorphism for new
operator we need factories to create wrapper for concrete ticket:
interface TicketFactoryI {
TicketI buy(...);
TicketI load(long externalId);
}
Concrete factory & ticket delegate work to concrete service:
interface Corp2TicketWrapper implements TicketI {
Corp2Service srv;
Corp2Ticket tkt;
constructor(Corp2Service srv, Corp2Ticket tkt) {
this.srv = srv;
this.tkt = tkt;
}
long getId() { return tkt.getId(); }
boolean isCancelled() { return tkt.isCancelled(); }
void applyDiscount() {
srv.applyDiscount(tkt);
}
void cancel() {
srv.cancel(tkt);
}
}
class Corp2TicketFactory implements TicketFactoryI {
Corp2Service srv;
TicketI buy(...) {
Corp2Ticket tkt = srv.buyTicket(...);
return new Corp2TicketWrapper(srv, tkt);
}
TicketI load(long externalId) {
Corp2Ticket tkt = srv.loadTicket(externalId);
return new Corp2TicketWrapper(srv, tkt);
}
}
Depending on CorpCode.CORP1
/ CorpCode.CORP2
/ etc we can provide factory of factory which would be the only place for implementation dispatching:
class TicketFactoryFactory {
static getInstance(CorpCode corporationCode) {
switch (corporationCode) {
case CorpCode.CORP1: return new Corp1TicketFactory();
case CorpCode.CORP2: return new Corp2TicketFactory();
case CorpCode.CORP3: return new Corp3TicketFactory();
}
}
}
Original program won't have references to concrete vendor code:
TicketFactoryI tktFactory = TicketFactoryFactory.getInstance(CorpCode.CORP2);
TicketI tkt = tktFactory.buyTicket(...);
tktEntity = ticketRepository.save(CorpCode.CORP2, tkt.getId(), Status.NEW);
...
tkt.applyDiscount();
...
tktEntity = ticketRepository.findById(id);
TicketI tkt = tktFactory.load(tktEntity.getExternalId());
if ( ! tkt.isCancelled()) {
tkt.cancel();
tktEntity.setStatus(State.CANCELLED);
ticketRepository.save(tktEntity);
}
Aucun commentaire:
Enregistrer un commentaire