Skip to content

Submission for March Pull Request#9

Open
nadimakk wants to merge 51 commits into
MarchPRfrom
Submission
Open

Submission for March Pull Request#9
nadimakk wants to merge 51 commits into
MarchPRfrom
Submission

Conversation

@nadimakk

@nadimakk nadimakk commented Apr 1, 2023

Copy link
Copy Markdown
Owner

No description provided.

nadimakk and others added 30 commits February 18, 2023 17:00
Run tests without building again.
Run tests without building again.
Included connection strings in environment of integration tests to remove them from app_settings.
using (_logger.BeginScope(new Dictionary<string, object>
{
{"ConversationId", conversationId},
{"SenderUsername", request.SenderUsername}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Username

{
try
{
SendMessageResponse response = await _messageService.AddMessage(conversationId, false, request);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

it's not clear what the false represents here, use a named parameter to make it obvious.

try
{
var result = await _imageService.DownloadImage(imageId);
_logger.LogInformation("Downloaded image with id {id}.", imageId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove, only log info for things added / updated, not needed for get requests

try
{
var profile = await _profileService.GetProfile(username);
_logger.LogInformation("Profile of {Username} fetched.", username);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove

[HttpPost]
public async Task<ActionResult<Profile>> PostProfile(Profile profile)
{
using (_logger.BeginScope("{Profile}", profile))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Username

Comment on lines +3 to +10
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:61550",
"sslPort": 44359
}
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can be deleted

Comment on lines +22 to +29
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can be deleted

public interface IImageService
{
Task<UploadImageServiceResult> UploadImage(Image image);
Task<FileContentResult> DownloadImage(string imageId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

FileContentResult is the controller business, the service layer should not be aware that it is being called from a controller.


public async Task<SendMessageResponse> AddFirstMessage(string conversationId, SendMessageRequest request)
{
return await AddMessage(conversationId, true, request);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

use a named parameter to make it easy to read what the true represents

Comment on lines +65 to +67
ValidateConversationId(conversationId);
ValidateLimit(limit);
ValidateLastSeenConversationTime(lastSeenConversationTime);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good job on using private methods to improve readability

var result = await _messageStore.GetMessages(
conversationId, limit, orderBy, continuationToken, lastSeenConversationTime);

return new GetMessagesServiceResult

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

GetMessagesResult. If you have another one in the controller with the same name, the controller object should be called GetMessagesResponse

Comment on lines +63 to +64
await _imageService.DeleteImage(profile.ProfilePictureId);
await _profileStore.DeleteProfile(username);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do it in parallel:

await Task.WhenAll(
_imageService.DeleteImage(profile.ProfilePictureId),
_profileStore.DeleteProfile(username)
);


public class UserConversationService : IUserConversationService
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

remove blank line

Comment on lines +31 to +39
if (!await _profileService.ProfileExists(username1))
{
throw new ProfileNotFoundException($"A profile with the username {username1} was not found.");
}

if (!await _profileService.ProfileExists(username2))
{
throw new ProfileNotFoundException($"A profile with the username {username2} was not found.");
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

await Task.WhenAll(request.Participants.Select(username => ThrowIfParticipantNotFound(username)))

and this can be done in a private function:

await EnsureThatParticipantsExist(request.Participants);

{
await AddMultipleUserConversations(_userConversation1, _userConversation2, _userConversation3);

var response = await _userConversationStore.GetUserConversations(_userConversation.Username, 1, OrderBy.ASC, null, 1);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please always use named parameters when it's not clear what the parameter is. In this case, it's hard to guess what 1, null and 1 are without looking at the implementation

await AddMultipleUserConversations(_userConversation1, _userConversation2, _userConversation3);

var response = await _userConversationStore.GetUserConversations(_userConversation.Username, 1, OrderBy.ASC, null, 1);
Assert.Equal(1, response.UserConversations.Count);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I believe you can use Assert.Single in this case

response = await _userConversationStore.GetUserConversations(_userConversation.Username, 3, OrderBy.ASC, null, 1);
Assert.Equal(3, response.UserConversations.Count);

await DeleteMultipleUserConversations(_userConversation1, _userConversation2, _userConversation3);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this should be done in the cleanup not here because cleanup is executed even if the test fails halfway through

Comment on lines +187 to +190
if(_userConversation1.LastModifiedTime > lastSeenConversationTime) { userConversationsExpected.Add(_userConversation1); }
if(_userConversation2.LastModifiedTime > lastSeenConversationTime) { userConversationsExpected.Add(_userConversation2);}
if(_userConversation3.LastModifiedTime > lastSeenConversationTime) { userConversationsExpected.Add(_userConversation3);}
if(_userConversation.LastModifiedTime > lastSeenConversationTime) { userConversationsExpected.Add(_userConversation);}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if you had a list of conversation you can do this in a for loop instead of repeating 4 times the same code


public async Task DisposeAsync()
{
await _userConversationStore.DeleteUserConversation(_userConversation.Username, _userConversation.ConversationId);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this cleanup can be used to delete all conversations, if you put them in a list

Comment on lines +53 to +67
UserConversation userConversation1 = new UserConversation
{
Username = username1,
ConversationId = conversationId,
LastModifiedTime = unixTimeNow
};
await _userConversationStore.CreateUserConversation(userConversation1);

UserConversation userConversation2 = new UserConversation
{
Username = username2,
ConversationId = conversationId,
LastModifiedTime = unixTimeNow
};
await _userConversationStore.CreateUserConversation(userConversation2);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

await Task.WhenAll(
CreateUserConversation(username1, conversationId, unixTimeNow),
CreateUserConversation(username2, conversationId, unixTimeNow),
)

Faster and easier on the eye :)


foreach (UserConversation userConversation in userConversations)
{
string[] usernames = userConversation.ConversationId.Split('_');

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The "_" separator is spread in multiple places. If we decide to change it, we have to change multiple places in the code, which is not ideal. This separator should live in one place and be hidden from the rest of the application.

Comment on lines +53 to +58
UserConversation userConversation1 = new UserConversation
{
Username = username1,
ConversationId = conversationId,
LastModifiedTime = unixTimeNow
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is a conceptual issue here. The UserConversation object doesn't say who is the other user involved in the conversation but the storage layer is not aware that the conversationId is a concatenation of usernames. I think you have two choices here:
1- The storage layer would own the knowledge of the separator being used and hide it from the rest of the application. You would pass in both usernames and ask the storage layer to create a conversation. The storage layer would create the conversationId using the concatenation and store the user conversation. The getUserConversations would return UserConversation list where each UserConversation has both participants. That way, the storage layer completely encapsulates the separator logic.
2- The service layer owns the separator logic. In this case, we should still pass both usernames and have them stored in the database because the storage layer doesn't know that the conversation id is a concatenations of usernames.

You current solution is a mix of the two approaches above, which is problematic because the logic is distributed in multiple places and the storage layer depends on the service layer indirectly (and vise versa).

recipientUsername = usernames[0];
}

Profile recipientProfile = await _profileService.GetProfile(recipientUsername);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

every time you have multiple await calls, you should consider doing them in parallel if possible

partitionKey: new PartitionKey(username),
new ItemRequestOptions
{
ConsistencyLevel = ConsistencyLevel.Session

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

move this to a private field instead of repeating every time, to make it easier to change the consistency level if needed (instead of having to change in multiple places).

try
{
IQueryable<UserConversationEntity> query = Container
.GetItemLinqQueryable<UserConversationEntity>(false, continuationToken, options)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

named parameter to show what the false is

{
Task AddMessage(string conversationId, Message message);
Task<Message> GetMessage(string conversationId, string messageId);
Task<(List<Message> Messages, string NextContinuationToken)> GetMessages(

@nehmebilal nehmebilal Apr 4, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is what mine looks like:

Task<GetMessagesResult> GetMessages(string conversationId, GetMessagesParameters parameters);

this works too:

Task<GetMessagesResult> GetMessages(GetMessagesParameters parameters);

Your signature is a bit too verbose.

public interface IProfileStore
{
Task AddProfile(Profile profile);
Task<Profile?> GetProfile(string username);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I noticed that in other store classes your throwing an exception if the entity doesn't exist. You may want to choose one approach instead of mixing. Both are fine but nullable is a bit cleaner in this case.

{
Task CreateUserConversation(UserConversation userConversation);
Task<UserConversation> GetUserConversation(string username, string conversationId);
Task<(List<UserConversation> UserConversations, string NextContinuationToken)> GetUserConversations(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar to messages, this signature can be simplified

@@ -0,0 +1,14 @@
namespace ChatService.Web.Utilities;

public class ConversationIdUtilities

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This class should be also responsible of splitting and should be the only class aware of the "_" separator

@nehmebilal nehmebilal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Alright enough comments for this one :)
Looks pretty good overall, good job! Please make sure to address the comments and apply them to other similar cases in the code. I will review the changes in your next PR.

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.

3 participants