Bewahren Sie die Geschichte von Reddit Post-Einreichungen auf
1. Überblick
In dieser Ausgabe vonthe Reddit App case study werden wir beginnen,the history of submission attempts for a post zu verfolgen und die Status beschreibender und verständlicher zu gestalten.
2. Verbesserung derPost-Entität
Lassen Sie uns zunächst den alten String-Status in der EntitätPostdurch eine viel vollständigere Liste der Übermittlungsantworten ersetzen und dabei viel mehr Informationen nachverfolgen:
public class Post {
...
@OneToMany(fetch = FetchType.EAGER, mappedBy = "post")
private List submissionsResponse;
}
Lassen Sie uns als Nächstes sehen, was wir in dieser neuen Übermittlungsantwort-Entität tatsächlich verfolgen:
@Entity
public class SubmissionResponse implements IEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private int attemptNumber;
private String content;
private Date submissionDate;
private Date scoreCheckDate;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "post_id", nullable = false)
private Post post;
public SubmissionResponse(int attemptNumber, String content, Post post) {
super();
this.attemptNumber = attemptNumber;
this.content = content;
this.submissionDate = new Date();
this.post = post;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Attempt No ").append(attemptNumber).append(" : ").append(content);
return builder.toString();
}
}
Beachten Sie, dass jedesconsumed submission attempt einSubmissionResponse hat, so dass:
-
attemptNumber: Die Nummer dieses Versuchs
-
content: Die detaillierte Antwort dieses Versuchs
-
submissionDate: das Einreichungsdatum dieses Versuchs
-
scoreCheckDate: Das Datum, an dem wir die Punktzahl der RedditPost bei diesem Versuch überprüft haben
Und hier ist das einfache Spring Data JPA-Repository:
public interface SubmissionResponseRepository extends JpaRepository {
SubmissionResponse findOneByPostAndAttemptNumber(Post post, int attemptNumber);
}
3. Planungsservice
Wir müssen jetzt mit dem Ändern der Service-Schicht beginnen, um diese zusätzlichen Informationen im Auge zu behalten.
Wir werden zunächst sicherstellen, dass wir einen gut formatierten Erfolgs- oder Misserfolgsgrund dafür haben, warum die Post als Erfolg oder Misserfolg angesehen wurde:
private final static String SCORE_TEMPLATE = "score %d %s minimum score %d";
private final static String TOTAL_VOTES_TEMPLATE = "total votes %d %s minimum total votes %d";
protected String getFailReason(Post post, PostScores postScores) {
StringBuilder builder = new StringBuilder();
builder.append("Failed because ");
builder.append(String.format(
SCORE_TEMPLATE, postScores.getScore(), "<", post.getMinScoreRequired()));
if (post.getMinTotalVotes() > 0) {
builder.append(" and ");
builder.append(String.format(TOTAL_VOTES_TEMPLATE,
postScores.getTotalVotes(), "<", post.getMinTotalVotes()));
}
if (post.isKeepIfHasComments()) {
builder.append(" and has no comments");
}
return builder.toString();
}
protected String getSuccessReason(Post post, PostScores postScores) {
StringBuilder builder = new StringBuilder();
if (postScores.getScore() >= post.getMinScoreRequired()) {
builder.append("Succeed because ");
builder.append(String.format(SCORE_TEMPLATE,
postScores.getScore(), ">=", post.getMinScoreRequired()));
return builder.toString();
}
if (
(post.getMinTotalVotes() > 0) &&
(postScores.getTotalVotes() >= post.getMinTotalVotes())
) {
builder.append("Succeed because ");
builder.append(String.format(TOTAL_VOTES_TEMPLATE,
postScores.getTotalVotes(), ">=", post.getMinTotalVotes()));
return builder.toString();
}
return "Succeed because has comments";
}
Jetzt verbessern wir die alte Logik undkeep track of this extra historical information:
private void submitPost(...) {
...
if (errorNode == null) {
post.setSubmissionsResponse(addAttemptResponse(post, "Submitted to Reddit"));
...
} else {
post.setSubmissionsResponse(addAttemptResponse(post, errorNode.toString()));
...
}
}
private void checkAndReSubmit(Post post) {
if (didIntervalPass(...)) {
PostScores postScores = getPostScores(post);
if (didPostGoalFail(post, postScores)) {
...
resetPost(post, getFailReason(post, postScores));
} else {
...
updateLastAttemptResponse(
post, "Post reached target score successfully " +
getSuccessReason(post, postScores));
}
}
}
private void checkAndDeleteInternal(Post post) {
if (didIntervalPass(...)) {
PostScores postScores = getPostScores(post);
if (didPostGoalFail(post, postScores)) {
updateLastAttemptResponse(post,
"Deleted from reddit, consumed all attempts without reaching score " +
getFailReason(post, postScores));
...
} else {
updateLastAttemptResponse(post,
"Post reached target score successfully " +
getSuccessReason(post, postScores));
...
}
}
}
private void resetPost(Post post, String failReason) {
...
updateLastAttemptResponse(post, "Deleted from Reddit, to be resubmitted " + failReason);
...
}
Beachten Sie, was die untergeordneten Methoden tatsächlich tun:
-
addAttemptResponse(): Erstellt einen neuenSubmissionResponse-Datensatz und fügt ihn dem Beitrag hinzu (wird bei jedem Übermittlungsversuch aufgerufen).
-
updateLastAttemptResponse(): Aktualisiert die Antwort des letzten Versuchs (wird aufgerufen, während die Punktzahl des Posts überprüft wird).
4. Geplante Post-DTO
Als Nächstes ändern wir das DTO, um sicherzustellen, dass diese neuen Informationen dem Client wieder zugänglich gemacht werden:
public class ScheduledPostDto {
...
private String status;
private List detailedStatus;
}
Und hier sind die einfachenSubmissionResponseDto:
public class SubmissionResponseDto {
private int attemptNumber;
private String content;
private String localSubmissionDate;
private String localScoreCheckDate;
}
Wir werden auch die Konvertierungsmethode in unserenScheduledPostRestController ändern:
private ScheduledPostDto convertToDto(Post post) {
...
List response = post.getSubmissionsResponse();
if ((response != null) && (response.size() > 0)) {
postDto.setStatus(response.get(response.size() - 1).toString().substring(0, 30));
List responsedto =
post.getSubmissionsResponse().stream().
map(res -> generateResponseDto(res)).collect(Collectors.toList());
postDto.setDetailedStatus(responsedto);
} else {
postDto.setStatus("Not sent yet");
postDto.setDetailedStatus(Collections.emptyList());
}
return postDto;
}
private SubmissionResponseDto generateResponseDto(SubmissionResponse responseEntity) {
SubmissionResponseDto dto = modelMapper.map(responseEntity, SubmissionResponseDto.class);
String timezone = userService.getCurrentUser().getPreference().getTimezone();
dto.setLocalSubmissionDate(responseEntity.getSubmissionDate(), timezone);
if (responseEntity.getScoreCheckDate() != null) {
dto.setLocalScoreCheckDate(responseEntity.getScoreCheckDate(), timezone);
}
return dto;
}
5. Vorderes Ende
Als nächstes werden wir unsere Front-End-scheduledPosts.jspändern, um unsere neue Antwort zu verarbeiten:
Detailed Status
6. Tests
Schließlich werden wir einen einfachen Unit-Test unserer neuen Methoden durchführen:
Zuerst testen wir die Implementierung vongetSuccessReason():
@Test
public void whenHasEnoughScore_thenSucceed() {
Post post = new Post();
post.setMinScoreRequired(5);
PostScores postScores = new PostScores(6, 10, 1);
assertTrue(getSuccessReason(post, postScores).contains("Succeed because score"));
}
@Test
public void whenHasEnoughTotalVotes_thenSucceed() {
Post post = new Post();
post.setMinScoreRequired(5);
post.setMinTotalVotes(8);
PostScores postScores = new PostScores(2, 10, 1);
assertTrue(getSuccessReason(post, postScores).contains("Succeed because total votes"));
}
@Test
public void givenKeepPostIfHasComments_whenHasComments_thenSucceed() {
Post post = new Post();
post.setMinScoreRequired(5);
post.setKeepIfHasComments(true);
final PostScores postScores = new PostScores(2, 10, 1);
assertTrue(getSuccessReason(post, postScores).contains("Succeed because has comments"));
}
Als nächstes werden wir die Implementierung vongetFailReason()testen:
@Test
public void whenNotEnoughScore_thenFail() {
Post post = new Post();
post.setMinScoreRequired(5);
PostScores postScores = new PostScores(2, 10, 1);
assertTrue(getFailReason(post, postScores).contains("Failed because score"));
}
@Test
public void whenNotEnoughTotalVotes_thenFail() {
Post post = new Post();
post.setMinScoreRequired(5);
post.setMinTotalVotes(15);
PostScores postScores = new PostScores(2, 10, 1);
String reason = getFailReason(post, postScores);
assertTrue(reason.contains("Failed because score"));
assertTrue(reason.contains("and total votes"));
}
@Test
public void givenKeepPostIfHasComments_whenNotHasComments_thenFail() {
Post post = new Post();
post.setMinScoreRequired(5);
post.setKeepIfHasComments(true);
final PostScores postScores = new PostScores(2, 10, 0);
String reason = getFailReason(post, postScores);
assertTrue(reason.contains("Failed because score"));
assertTrue(reason.contains("and has no comments"));
}
7. Fazit
In diesem Teil haben wir einige sehr nützliche Einblicke in den Lebenszyklus eines Reddit-Posts gegeben. Wir können jetzt jedes Mal genau sehen, wann ein Beitrag gesendet und gelöscht wurde, zusammen mit dem genauen Grund für jeden Vorgang.