Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👌 IMPROVE: make repository robust in treating network failures #361

Merged
merged 6 commits into from
May 31, 2024

Conversation

violoncelloCH
Copy link
Member

@violoncelloCH violoncelloCH commented May 28, 2024

closes #360

@violoncelloCH violoncelloCH self-assigned this May 28, 2024
@violoncelloCH violoncelloCH added this to the Milestone 4 milestone May 28, 2024
@violoncelloCH violoncelloCH force-pushed the feature/repository-network-robustness branch 3 times, most recently from bd0c0c4 to 9f15e72 Compare May 30, 2024 09:53
@violoncelloCH violoncelloCH marked this pull request as ready for review May 30, 2024 09:53
@octogradiste octogradiste self-requested a review May 30, 2024 15:22
Copy link
Contributor

@octogradiste octogradiste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again massive work! Thank you so much! Consider creating a wrapper function for the supabase data source. This would avoid some duplication and make it cleaner :). Ottherwise, looks really good to me!

try {
remoteDataSource.getAssociation(associationId)
} catch (e: HttpRequestException) {
return localDataSource.getAssociation(associationId, FETCH_ALL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you are using the try catch block as an expression you can drop the return key word (the last statement is automatically returned). You can also do this in all try catch blocks below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for you feedback.
Taking a close look on this, I realize that no, this is not the case. The return is intentional here to exit the function.
If you check the line above the try (currently 49), there is a variable assignment done. Based on that variable assignment, further actions are taken. However in case of the exception the result from line 53 needs to be returned directly as otherwise it would be written back to the local database unnecessarily.

delay(RETRY_DELAY_MILLI)
return getAssociation(associationId, maxRetriesCount - 1u)
}
else -> throw e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could write a function which wraps the supabase call and reuse it everywhere. It could look like this:

/**
 * Wrapper for Supabase requests that retries the request if it fails due to a
 * [HttpRequestException]. If the request fails after [maxRetriesCount] retries, it throws the
 * exception. In case of an [NoSuchElementException], it returns null.
 *
 * @param maxRetriesCount: the number of times the request will be retried
 * @param request: the request to be executed
 */
private suspend fun <T> supabaseRequestWrapper(
    maxRetriesCount: UInt,
    request: suspend () -> T,
): T? {
    return try {
        request()
    } catch (e: NoSuchElementException) {
        null
    } catch (e: HttpRequestException) {
        if (maxRetriesCount == 0u) throw e
        delay(RETRY_DELAY_MILLI)
        return supabaseRequestWrapper(maxRetriesCount - 1u, request)
    }
}

And you could use it like this:

override suspend fun getAssociation(
    associationId: String,
    maxRetriesCount: UInt
): Association? = supabaseRequestWrapper(maxRetriesCount) {
    supabase
        .from("associations")
        .select(Columns.raw(QUERY_ASSOCIATION)) {
            filter { eq("association_id", associationId) }
        }
        .decodeSingle<AssociationSupabase>()
        .toAssociation()
}

Note that you would need to also write a wrapper fur function which return a list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Yes absolutely. I've been thinking about generalizing these. Actually there are quite a few things which are similar for each type of methods (single getters, getAlls, deletes, etc). However there are still some subtle differences for some of them and I'm unsure up to which point generalization makes sense. Even for the wrapper you're suggesting, there are actually some functions for which I check for different Exceptions.
I'll look into this and see if I can build something reasonable without breaking the tests :D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so I heavily reworked this. Based on your suggestion I did a wrapper function for the retry and exception handling in the SupabaseDataSource. In a similar way, i reworked the code from the RepositoryImpl to remove redundancies as much as possible. (The changes are in the last two commits)

@violoncelloCH violoncelloCH changed the title 👌 IMPROVE: make repository robust in threating network failures 👌 IMPROVE: make repository robust in treating network failures May 30, 2024
@violoncelloCH violoncelloCH force-pushed the feature/repository-network-robustness branch from 9f15e72 to ca45a81 Compare May 31, 2024 00:29
Copy link
Contributor

@octogradiste octogradiste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The code already looks much cleaner. Just some minor changes to make it even more clean ;)

associations: List<String>,
maxRetriesCount: UInt
): List<Association> =
supabaseRequestExceptionHandlerAndRetryWrapperS(maxRetriesCount) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the normal wrapper and use ?: emptyList() so that it returns an empty list when the operation fails.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh yes, good Idea! I was too tired yesterday evening to come up with a cleaner solution 😅
thanks!

is Tag -> this.tagId
is UserProfile -> this.userId
else -> ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a generic type DataObject, I'd suggest you use a sealed class DataModel for all our data model, this would make it typesafe and avoid the else branch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried creating a supertype for the dataclasses, but somehow dataclasses can't inherit in Kotlin. I was not aware that I could use sealed classes for this. Indeed it is the best solution for this! Thank you very much!

Copy link
Contributor

@octogradiste octogradiste left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much! Now the code is really clean!

@violoncelloCH violoncelloCH force-pushed the feature/repository-network-robustness branch from 32f3009 to 89795fd Compare May 31, 2024 08:35
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
64.85% Line Coverage on New Code (required ≥ 80.0%)

See analysis details on SonarCloud

@violoncelloCH violoncelloCH merged commit 0b106f8 into main May 31, 2024
1 of 2 checks passed
@violoncelloCH violoncelloCH deleted the feature/repository-network-robustness branch May 31, 2024 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Make the repository more robust
2 participants