-
Notifications
You must be signed in to change notification settings - Fork 1
Complex code sections
This page documents a complex code sections within the project. Here, you'll find a detailed breakdown to enhance understanding. Expect to find line-by-line explanations, comments, and potentially diagrams or visualizations for clarity.
The Layout component serves as the root layout for the application. It sets up the necessary providers and themes for the entire app. Here's a breakdown:
- It imports necessary dependencies like
QueryClientProvider,CurrentUserContextProvider, andAuthProviderto manage state and data fetching. - It defines a custom theme using
react-native-paperwith primary and secondary colors overridden. - Inside the
QueryClientProvider, it wraps theCurrentUserContextProviderandAuthProvider, ensuring that the entire app has access to user authentication and current user context. - The
Slotcomponent is rendered within thePaperProvider, which acts as a placeholder for rendering different screens/components based on navigation.
The Layout component responsible for checking user authentication status. Here's what it does:
- It manages the loading state while verifying the user's authentication status.
- Utilizes hooks like
useRefreshTokenanduseAuthto manage authentication-related functionality. - If the user is not authenticated (no access token), it redirects to the login screen.
- If users token is not active(user just registered his account) it renders preferences page, otherwise it renders main app
This component manages the layout for protected routes, ensuring that only authenticated users can access certain screens. Here are the details:
- It uses
Tabsfromexpo-routerto create a tab-based navigation interface. - Each tab represents a different screen, with corresponding icons and options.
This component handles the layout for setting up user preferences, divided into multiple steps. Here's how it works:
- It uses
createStackNavigatorfrom@react-navigation/stackto create a stack-based navigation flow. - Each step of preference setup is represented by a separate screen component.
Match service contains the most important code for out application. It is important for us that code is both simple and fast. So with this analasys we will go through all methods that are contained in match service. Methods in match service are as follows:
findMatchfinishMatchfindPotentialMatchesfilterUsersByPreferencesgetAllSuccessfulMatchedUsersfindMatchedUserById
public Match findMatch(String userId1, String userId2) {
Optional<Match> match1 = matchRepository.findByFirstUserIdAndSecondUserId(userId1, userId2);
if (match1.isPresent()) {
return match1.get();
}
Optional<Match> match2 = matchRepository.findByFirstUserIdAndSecondUserId(userId2, userId1);
return match2.orElse(null);
}Control flow for findMatch:
1 decision point (if (match1.isPresent())) Cyclomatic complexity: 𝑀=𝐸−𝑁+2𝑃 𝐸=4,𝑁=3,𝑃=1 𝑀=4−3+2 𝑀=3
public Match finishMatch(Match match, boolean status) {
match.setFinished(true);
match.setAccepted(status);
return matchRepository.save(match);
}Control flow for finishMatch:
No decision points Cyclomatic complexity: M=E−N+2P E=3,N=3,P=1 M=3−3+2 M=2
public List<DisplayUserDto> findPotentialMatches(User user, int skip) {
List<User> mappedResults = mongoTemplate.aggregate(
Aggregation.newAggregation(
Aggregation.sort(Direction.DESC, "_id"),
Aggregation.match(
Criteria.where("active").is(true)
.and("_id").nin(user.getSeenUserIds())
.and("age").lte(user.getPreferences().getAgeGroupMax())
.and("gender").is(user.getPreferences().getPartnerGender())
.and("location").is(user.getLocation())
.and("email").ne(user.getEmail())),
Aggregation.match(Criteria.where("age").gte(user.getPreferences().getAgeGroupMin())),
Aggregation.skip(skip),
Aggregation.limit(5)),
"users", User.class).getMappedResults();
List<User> filteredList = new ArrayList<>();
filterUsersByPreferences(user, mappedResults, filteredList);
return DisplayUserConverter.convertToDtoList(filteredList);
}Control flow for findPotentialMatches:
No decision points directly within this method Cyclomatic complexity: M=E−N+2P E=3,N=3,P=1 M=3−3+2 M=2
public List<DisplayUserDto> getAllSuccessfulMatchedUsers(User user) {
MatchOperation matchOperation = Aggregation.match(
Criteria.where("finished").is(true)
.and("accepted").is(true)
.andOperator(
new Criteria().orOperator(
Criteria.where("firstUserId").is(user.getId().toString()),
Criteria.where("secondUserId").is(user.getId().toString()))));
ProjectionOperation projectionOperation = Aggregation.project().and(
ConditionalOperators
.when(ComparisonOperators.Eq.valueOf("firstUserId").equalToValue(user.getId().toString()))
.thenValueOf("secondUserId")
.otherwiseValueOf("firstUserId"))
.as("matchedUserId");
Aggregation aggregation = Aggregation.newAggregation(matchOperation, projectionOperation);
List<MatchedUserIdDTO> matchedIds = mongoTemplate.aggregate(aggregation, "matches", MatchedUserIdDTO.class).getMappedResults();
List<String> matchedUserIds = matchedIds.stream()
.map(matchedUserDTO -> matchedUserDTO.getMatchedUserId())
.collect(Collectors.toList());
List<DisplayUserDto> userDtos = DisplayUserConverter.convertToDtoList(userRepository.findAllById(matchedUserIds));
for (DisplayUserDto userDto : userDtos) {
userDto.setChatId(findMatch(user.getId(), userDto.getId()).getId());
}
Map<String, Message> lastMessages = new HashMap<>();
for (DisplayUserDto userDto : userDtos) {
lastMessages.put(userDto.getId(), messageRepository.findFirstByChatIdOrderByTimestampDesc(userDto.getChatId()));
}
userDtos.sort(Comparator.comparing((DisplayUserDto userDto) -> {
Message lastMessage = lastMessages.get(userDto.getId());
return (lastMessage != null) ? lastMessage.getTimestamp() : null;
}, Comparator.nullsLast(Comparator.reverseOrder())));
return userDtos;
}Control flow for getAllSuccessfulMatchedUsers:
1 decision point (if (lastMessage != null) inside the sort comparator) Cyclomatic complexity: M=E−N+2P E=4,N=3,P=1 M=4−3+2 M=3
public static void filterUsersByPreferences(User currentUser, List<User> userList, List<User> filteredList) {
for (User user : userList) {
Preferences listPreferences = user.getPreferences();
if (listPreferences != null) {
// Check age group
if (currentUser.getPreferences() != null &&
(currentUser.getAge() < listPreferences.getAgeGroupMin() ||
currentUser.getAge() > listPreferences.getAgeGroupMax())) {
continue; // Skip this user
}
// Check gender preference
if (currentUser.getPreferences() != null &&
!currentUser.getGender().equals(listPreferences.getPartnerGender())) {
continue; // Skip this user
}
// Add the user to the filtered list
filteredList.add(user);
}
}
}Control flow for filterUsersByPreferences:
3 decision points Cyclomatic complexity: M=E−N+2P M=10−5+2×1 M=10−5+2 M=7
Profiling was conducted using the built-in IntelliJ profiler in IntelliJ IDEA. The requests were simulated through a Postman run.
In the profiling output, the yellow-highlighted methods represent our application-specific methods.
-
JwtAuthenticationFilter.doFilterInternalMethod- This method appears to consume a significant amount of processing time.
- Filters in a web application act like middleware, executing on each request.
- Upon closer inspection, our custom code within this filter takes approximately 250 ms.
- The majority of the time is spent in the
doFiltermethod of the next filter, which proceeds to call the subsequent filter in the chain. - Conclusion: Our implementation in this method is performing adequately.
-
Fetching Matched Users
- Methods related to fetching single or multiple matched users show potential for performance improvements.
- These methods search the matches collection using user IDs stored under the fields
firstUserIdandsecondUserId. - Potential Issue: These fields are not indexed, leading to slower search operations.
- Optimization Suggestion: Index the
firstUserIdandsecondUserIdfields to improve search performance.
Below are screenshots from the profiling session:
This summary highlights the methods consuming the most processing time. The JwtAuthenticationFilter.doFilterInternal is a notable entry.
This detail shows the breakdown of time spent in various methods within the application.
The time spent on fetching matched users indicates a potential slowdown due to the lack of indexing on critical fields.
-
Optimize Filtering Process:
- Review the
JwtAuthenticationFilter.doFilterInternalimplementation to ensure it’s as efficient as possible. - Consider optimizing or simplifying the logic if feasible, though current performance seems reasonable.
- Review the
-
Index Database Fields:
- Index the
firstUserIdandsecondUserIdfields in the matches collection to enhance search performance. - This should significantly reduce the time required for fetching matched users.
- Index the
By addressing these areas, we can improve the overall performance of the application, ensuring faster response times and a smoother user experience.