The Identity Paradox | DDD, EF Core & Strongly Typed IDs

  Рет қаралды 27,596

Amichai Mantinband

Amichai Mantinband

Күн бұрын

Why does everyone recommend using strongly typed IDs if this doesn't work with EF Core?
This video will break it down and show a creative hack that elegantly solved this issue.
Get the source code: / amantinband .
Link to the entire playlist: • ASP.NET 6 REST API Fol...
Connect with me on 'em socials:
Twitter: / amantinband
LinkedIn: / amantinband
GitHub: github.com/amantinband
00:00. The Problem
07:40. Paradoxical Solutions 👎
10:53. Better Solution 👍

Пікірлер: 103
@ahmedrizk106
@ahmedrizk106 Жыл бұрын
I'm actually one of the people who opened a github issue for this, and let me just say after weeks of exhaustive solution implementations I'm really glad you are back. 👏❤
@misonosenpai3168
@misonosenpai3168 5 ай бұрын
Now this problem is fixed on EF core 8, but this is a very helpful tutorial, i can't find any course that teach about DDD better than your course
@troelsmortensen9914
@troelsmortensen9914 5 ай бұрын
Do you by chance have a link to where I can see this solution? I can't find the fix.
@OldShoolGames
@OldShoolGames 4 ай бұрын
Do you have any link to it ?
@yougayan
@yougayan 29 күн бұрын
Yep, this is definitely fixed in EF Core 8, just tried it.
@MilanJovanovicTech
@MilanJovanovicTech Жыл бұрын
Good to see you back 🔥
@nawarali1912
@nawarali1912 Жыл бұрын
why you don't do something together 😄 you both provide the domain driven design and design patterns in the best way
@alan-
@alan- Жыл бұрын
@@nawarali1912 I agree. Milan + Amichai = CA + DDD
@poddev
@poddev Жыл бұрын
Yes I agree is nice to have our ids properties strongly tipped in the other hand sometimes I feel this is too over engineering which is the opposite of clean code.
@amantinband
@amantinband Жыл бұрын
I tend to agree. I think there very specific applications need to follow these practices to the T
@jorgeurielcarballohernande9886
@jorgeurielcarballohernande9886 9 ай бұрын
Wow! I started this amazing serie about CA and DDD and I can't stop. Congrats for your job. No words to describe the effort and the passion put in this. Again thousand thanks @Amichai 👌🤓.
@shoooozzzz
@shoooozzzz Жыл бұрын
Ahhhhh yeah, he's back! So happy we get more top tier content.
@carloswinstonjavierllenas3117
@carloswinstonjavierllenas3117 Жыл бұрын
Many thanks. I found myself Laughing Out Loud when you reached the not recommended solutions because I tried the first three in my pet project where I'm trying all you taught us in these videos, and discarded the first two because of the SAME reasons.
@alexandercarlsen2038
@alexandercarlsen2038 Жыл бұрын
I usually keep the inner identifier private and implement implicit and explicit conversions to string or whichever inner type we are using. This way, all of the domain code doesn't know, but any dependencies (like EF or something like a httpclient can use the string as needed)
@amantinband
@amantinband Жыл бұрын
I like that approach as well
@matthewrossee
@matthewrossee 9 ай бұрын
Do you have any repo on GitHub where I could look at your solution? It seems interesting.
@davemasters
@davemasters Жыл бұрын
Back with a bang! I struggled with this and ended up succumbing to your 2nd bad solution. Look forward to going back to implement this solution!
@qaphuikpoh
@qaphuikpoh Жыл бұрын
Good to see you back 🎉
@Codewrinkles
@Codewrinkles Жыл бұрын
Nice one. Welcome back!
@LoZioIAR
@LoZioIAR Жыл бұрын
Great!! You are back!!
@prashlovessamosa
@prashlovessamosa Жыл бұрын
Long time pal.
@MaximShiryaevT
@MaximShiryaevT Жыл бұрын
IMHO, the main problem is an existence of Id properties in the domain objects. If we've got rid of foreign keys in dependent objects using shadow properties, for me it's just absolutely necessary to make Id properties shadow as well. No Id - no problem. Ids are DB realm concept, not object one. What do you think?
@amantinband
@amantinband Жыл бұрын
Have you tried this in a project before? Do you have an example you can send me?
@jamesevans6438
@jamesevans6438 Жыл бұрын
Welcome back - I've never modeled a domain to this extent using EF Core so not hit the problem you were facing but I like the solution, seems nice and clean, no real downside?
@amantinband
@amantinband Жыл бұрын
It has some overhead, complexity and requires breaking “persistence ignorance”, but out of the solutions, this is definitely better IMO
@baulron
@baulron 3 ай бұрын
Amazing content. Thank you!
@vagnerpadilha3485
@vagnerpadilha3485 Жыл бұрын
Amazing to see you again. A question. why do you prefer StronglyId has a "class" type Wouldn't "record struct" or "struct" be preferable? To lower the GC pressure?
@amantinband
@amantinband Жыл бұрын
Generally yes. The architecture I’m demonstrating in this series isn’t very GC friendly, especially with all the various objects and MediatR, so memory/runtime sensitive applications should probably model their system differently. But to your question, I haven’t given struct enough thought here to say if non-destructive mutation or stack allocations can present issues. I’ll have to play with it and come back to you 🙂
@tplummer217
@tplummer217 Жыл бұрын
Great stuff, thanks!
@martinmagpantay4226
@martinmagpantay4226 7 ай бұрын
Hi Amichai! I have a question, For queries with multiple related entities like getting the Menu with the list of Dinners and MenuReview which sits on different Aggregate Root. What do you think you'll use on that? I'm so happy to subscribe to your Patreon. You're such a blessing in the community. :)
@martinmagpantay4226
@martinmagpantay4226 7 ай бұрын
Also, for querying only details of an entity which is not an aggregate root. Thank you!
@atlesmelvr1997
@atlesmelvr1997 Жыл бұрын
It's not a common bug to put the wrong id in the wrong column, and you can even enforce to write them as (userId: userId, tenantId: tenantId) to read it better. And the penalties you get is a lot worse than what you get (that's not needed). You have less readability, more code to write everywhere, it's slower and use more cpu. All this for a non problem.
@amantinband
@amantinband Жыл бұрын
I share your opinion most of the time. I’m covering the religious DDD approach for educational purposes. However, there are cases where strongly typed IDs can be helpful, and I don’t think we should disregard them as a whole. I deal with versioned string-typed IDs that are a combination of 4 or 5 other IDs regularly within Microsoft. This is a prime example of where strongly typed IDs, regardless of DDD would make several code bases less error-prone
@timschmidt5469
@timschmidt5469 Жыл бұрын
Welcome back and awesome video! I love this series! I think the simplicity of the "creative hack" is worth the minor costs of developer dogmatism :) Your solution requires much less work and maintenance than "the right way" AND you're having to do this because of the limitations of known issues that pretty much have "Won't Fix" resolutions (at least not anytime soon). Fantastic job!
@amantinband
@amantinband Жыл бұрын
Thanks, Tim! 🙏
@jose49716
@jose49716 Жыл бұрын
Which technology are you using for that slides and arrows? It's pretty awesome.
@amantinband
@amantinband Жыл бұрын
Thanks! Slides - Figma. Arrows - Presentify.
@jose49716
@jose49716 Жыл бұрын
Thanks for letting me know!!! Great content.
@sphrtehrani
@sphrtehrani 10 ай бұрын
Hi, great series. In DDD we use GUID for Id type because entities ids must be unique across our domain. But in database using GUID as primary key (with clustered index) has performance issue specially in heavy insert scenarios because of randomness of GUID Ids. What can we do about it?
@nitrovent
@nitrovent 9 ай бұрын
There are solutions for index-friendly GUIDs such auto-incrementing GUIDs. The ABP framework e.g. implements a GUID generator that generates pseudo GUIDs for that purpose.
@md.redwanhossain6288
@md.redwanhossain6288 Ай бұрын
Use ULID based GUID
@radiosh66
@radiosh66 Жыл бұрын
Hi! Thanks for the video. I think it's too many generics in this solution- too complex. Btw, what tool do you use to draw on the screen?
@amantinband
@amantinband Жыл бұрын
Generics usually make the code more complex and harder to understand. I don't think most applications need this kind of overhead. I use Presentify for the arrows and rectangles 🙂
@radiosh66
@radiosh66 Жыл бұрын
@@amantinband Yes, thank you for the answer and for the great content!
@ayalamac
@ayalamac Жыл бұрын
Welcome back! Where were you? Missing your videos. Now, I see an upgrade in your drawings. What is the tool now? It seems it is not longer ZoomIt! Good job again!
@amantinband
@amantinband Жыл бұрын
Presentify. ZoomIt doesn't work on MacOS 😢
@maxbitran
@maxbitran 7 ай бұрын
My strongly typed Id are all structs and I have generic converters for json serialization and EF Core. In the database they all became strings, like the Guids, DateTimes (in most dbs), etc. There should be no reason for you to want to keep more than that in the database, if it is simply an Id and a value object. The problem you mentioned is related to complex objects, EF Core supports working with them and, if I am not mistaken, in future versions, they will support save them as json in the database, like document dbs. If you can give me more information, I can try to understand why this is such a big deal for you to make a whole video mentioning it as an "unsolvable" paradox. All the best.
@troelsmortensen9914
@troelsmortensen9914 4 ай бұрын
Was the correct part of the migration shown at the end? The example is about Host having many MenuIds. But we see a table of MenuDinnerIds, i.e. combining Menu and Dinner? And it looks like you would only get one foreign key constraint, back to the "owner", i.e. to Host (or, in the shown migration to, Menu). But there is no FK constraint on the MenuId, so you can have invalid references in the database? Generally I would expect the end result to be a join table in the database, with references to both Host and Menu. Your result ends up with only one foreign key.
@evgeniysir4220
@evgeniysir4220 11 ай бұрын
I have 2 unrelated sets of tables (Menus and Hosts) after adding the Host aggregate code and adding its tables to the database. Is this how it should be? Isn't the HostMenuIds table a many to many relationship table for two aggregates Menus and Hosts? Should these two aggregates be linked, or was it originally intended to make an unlinked set of aggregate tables for further division into microservices with separate bases?
@matthewrossee
@matthewrossee 9 ай бұрын
In the BuberDinner's codebase as far as I know you're supposed to raise a new domain event like "record MenuCreatedDomainEvent(MenuIn menuId, HostId hostId) : IDomainEvent". When saving changes to the database, the saving changes interceptors should publish this event and the MenuCreatedDomainEventHandler : INotificationHandler should take care of linking the MenuId to the Host. So it could look like this: var host = _hostRepository.GetById(notification.hostId), then check if host is not null, and eventually perform the linking operation: host.AssignMenuId(menuId). Remember not to call SaveChangesAsync, you don't need any unit of work in the event handler, because when all event handlers have finished their work the dbcontext is gonna save changes. I guess for a distributed system you could imagine a situation when domain event handlers publish some sort of integration event to a message queue like RabbitMq, so the other microservices can update their db state. I hope it helps!
@VahidRassouli
@VahidRassouli 8 ай бұрын
Hi and thank your for your great afford. I have a question! What about the MenuId column, in the HostMenuIds table? Shouldn't it be a foreign key to the Id column of the Menues table? Currently following you toturial, I'm missing this relation! And I think it's important to have it, in case of deleting menu, we can cascade it to delete the corresponding record in the HostMenuIds table Thank you very very much👌
@VahidRassouli
@VahidRassouli 8 ай бұрын
Well, I think I found the answer in the next video, Domain Events! As you mentioned there, aggregates are transaction boundary, And we dont want changing an aggregate to have side effect on others, and that's why we don't setup foreign keys! But this brings me to next question, so why do we try to set foriegn key for the host-menu relation as described in this video?! Deleting a host, would delete the menu... isn't a side effect?!
@md.redwanhossain6288
@md.redwanhossain6288 Ай бұрын
@@VahidRassouli This is very impractical. DDD doesn't need to be followed to the 100%. If you don't add FK, you are risking data integrity and there is no point of using relational database then. If you do not use cascade, there is no possibility of side effect.
@925082
@925082 Жыл бұрын
Mapster giving issue with record no default contractor for type RegisterCommand, Please use ConttructUsing or MapWith
@ahmedrizk106
@ahmedrizk106 Жыл бұрын
A question here, how would you implement a many to many relationship with this approach ? for example if you have an A-aggregate who owns list and this B-Aggregate is suppose to own List this would throw the same exception as before, how can we solve this?
@amantinband
@amantinband Жыл бұрын
This should work. Are you referencing a list of IDs in both?
@ahmedrizk106
@ahmedrizk106 Жыл бұрын
@@amantinband Yes I'm referencing a List of Ids in both and efcore throws the same type of exception when trying to add a migration. Aggregate-A has List Aggregate-B has List
@amantinband
@amantinband Жыл бұрын
@@ahmedrizk106 Perhaps try using the .NET 8 preview SDK. They fixed this error message (among others), it may give you insight to what the error is
@md.arafatrahmanrana242
@md.arafatrahmanrana242 Жыл бұрын
Strongly typed Id is really great, but the problem you faced I found out is in the EFCore migration generation process. If you mention configuration instances that are not having the "OwnedMany()" methods first and then mention the configuration instances that have "OwnedMany()" methods, then "EFCore" is able to generate the perfect migration files. Maybe this behavior is happening because of using reflection heavily. However, I'm not sure about this. Maybe you and other experts could find out that and can issue a bug to the Github repo. 😊
@mightybobka
@mightybobka 8 ай бұрын
Can it be solved by Complex Types as value object in new EF8.0?
7 ай бұрын
Not yet cover. Collections of complex types are not yet supportted. Vote for the issue 31237
@jamesroot9777
@jamesroot9777 Жыл бұрын
I am having issues migrating like this. It keeps telling me that (for example) no suitable c-tor was found for 'HostId', so I go ahead a make a paramterless one there (which is something you didn't have to do), then the error changes to the entity type 'HostId' requires a primary key.. etc. I essentially keep going in a loop, adding `HasNoKey()` to X Id Value Object, then it says that I cannot be Keyless, and suggests making the AggregateRootId keyless, which initself brings another error, and I just cannot get it to work for whatever reason, and my project is basically 1:1 with yours. Been debugging for a few hours now, reading stuff online, but nothing seems to be working. Would be great if anybody has suggestions.
@TheFeljoy
@TheFeljoy 11 ай бұрын
Watch closely in the video. He’s not going from the last version to this version but rather deleted the old one and is executing “add initialCreate” again
@blastermeteor9847
@blastermeteor9847 3 ай бұрын
I thought the object-type configured with OwnsMany should be recognized as a Non-Entity Type? Can someone explain cuz he said in the video that MenuId in HostAggregate config is an Entity Type
@user-nw8oi9vn9y
@user-nw8oi9vn9y 3 ай бұрын
Yeah, you can change a GUID to a string, but if you change a string id to a Guid, then you might break existing string values that don't meet the Guid requirements (unless I'm misunderstanding what you're recommending).
@Eirenarch
@Eirenarch Жыл бұрын
Currently implementing this is heavy (a lot of code and relatively complex code) that it does not justify the benefits (you also need to do work on the MVC side to make it map the ids). It would be cool if C# had some kind of type aliases where you can just give names to existing types which would make it simpler to use and support in libraries like EF and MVC as they would just need to recognize the actual type and treat the value as such while the compiler takes care of wrong usage
@shakeuk
@shakeuk Жыл бұрын
Couldn't you make use of user-defined explicit or implicit conversation on the strongly typed IDs? To make EF core see/use the encapsulated primitive?
@amantinband
@amantinband Жыл бұрын
That won’t work either. OwnsMany/OwnsOne defines the type as an entity type
@mohamedal-qadeery6530
@mohamedal-qadeery6530 Жыл бұрын
@@amantinband what do you mean by OwnsMany/OwnsOne defines the type as an entity type.. what do u mean by entity type ? this video made me so confused :(
@S3Kglitches
@S3Kglitches Жыл бұрын
Your domain-layer objects should not be your EF models. Breaking single responsibility principle. 8:00, 8:45 Mapping is overhead but that's the trade-off for robustness and having an anti-corruption layer. 9:15 Creating a mapping cannot introduce bugs compared to switching IDs in the function arguments which definitely will and these will be very hidden bugs.
@markcampbell2491
@markcampbell2491 Жыл бұрын
THANK YOU. Agree with you 100%
@amantinband
@amantinband Жыл бұрын
The objects created by EF Core’s fluent API definition *is* the anti corruption layer and the mapping
@GreenDimka1
@GreenDimka1 11 ай бұрын
​​@@amantinbandit is a very very very wrong use for an O/RM.
@ilyahryapko
@ilyahryapko Жыл бұрын
2:00 Static method signature should probably return ReservationId?
@amantinband
@amantinband Жыл бұрын
Yup 😀
@softwaretitbits5849
@softwaretitbits5849 Жыл бұрын
Entity framework migrations are only good for simple project. Pretty useless in a big company which needs the DB schema to be the same in all environments. Will MenuId:AggregateRoot not work if there in a implicit cast to string? For JSON serializer we need to give a conversion function. It was in my todo list that your video now reminded me to do.
@kmcdo
@kmcdo Жыл бұрын
First!
@ahmad_9877
@ahmad_9877 9 ай бұрын
One of the most genius rules about programming, that I really believe it: YOU ARE'NT GONNA NEED IT. So, apply abstractions when they really make a difference, not in the hope of some day that they will become useful. Also, the reasons that you mentioned to support this pattern are true about ALL fields, not just ids, so with this approach, maybe we will have one value object per field. I refer to Eric Evan's opinion in his book, when he talks about standalone objects, that making dependencies adds complexity to the code and demands more effort to understand it, so when you can express a field by a primitive type, you have the chance to eliminate one dependency, and you should definitely do it, especially when you have no logic to be encapsulated with that value.
@md.redwanhossain6288
@md.redwanhossain6288 Ай бұрын
Id and other fields are not the same. If id is wrong, the whole data will be in an invalid state.
@jwbonnett
@jwbonnett 11 ай бұрын
The part that I don't understand is that you said the ID is a VO, yet a VO should not have any identity, this is the key difference between an entity and VO.
@brendanalexander6053
@brendanalexander6053 Жыл бұрын
I have strongly typed my cats
@craigmunday3707
@craigmunday3707 Ай бұрын
Why are these ids called ValueObjects and not DomainPrimitives? Seems like a more descriptive name for them.
@DJHightower77
@DJHightower77 Жыл бұрын
The sound was very quiet in this video.
@svorskemattias
@svorskemattias Жыл бұрын
I've practiced domain driven with ef core for two years now without ever having to map up identities as entities. I don't understand why you would wanna do this? To me, it feels like this ain't a problem with the feature set of ef core, but some other misunderstanding on how you should model aggregates.
@amantinband
@amantinband Жыл бұрын
The IDs are value objects, not entities. If you’re interested in learning more, you can check this out: www.informit.com/articles/article.aspx?p=2020371&seqNum=4
@svorskemattias
@svorskemattias Жыл бұрын
@@amantinband I know that. Thats why i wouldnt configure them as entities, as you try to do.
@amantinband
@amantinband Жыл бұрын
Oh, perhaps I wasn't clear in the video - I am talking about EF Core entity types/non-entity types, not DDD entities.
@CodeBallast
@CodeBallast Жыл бұрын
Why make the Id property of the AggregateRootId abstract? Why not just implement it in the base class itself then you wouldn't need to override it in every inherited class.
@amantinband
@amantinband Жыл бұрын
You're right, what you're suggesting is better 🙂
@CodeBallast
@CodeBallast Жыл бұрын
@@amantinband Keep up the good work, champ!
@DummyFace123
@DummyFace123 Жыл бұрын
The best thing you can do concerning EF, is just use something that benefits your life and organization. EF doesn't provide any additional value, only introduces new problems. From knowing the intricacies about how the change tracker actually works, to knowing what linq is valid, what the valid linq translates to, to how easily it is for devteams to mangle the snapshot, to its obscured concurrency capabilities (that only the most knowledgeable devs know how to do), it is just never worth it. See how I didn't even mention sql performance? I don't think sql performance along is enough to choose an ORM. It really boils down to the enormous amount of learning that needs to occur in order to maintain EF applications. I've been working with it for over half of my career, and the development shops that wisely choose not to use EF are rewarded handsomely. Just use something like dapper and manage migrations through fluentmigrator or something similar. The non-EF shops ALWAYS have a much more pleasant time with data access and database maintenance. EF adds nothing but complexity and headaches
@ricardomartinez1
@ricardomartinez1 Жыл бұрын
“reasons”
@M1stFink
@M1stFink Ай бұрын
parameter objects for the function and unit testing. problem solved. Why coming up with some new creative ideas to clutter a project with yet another approach for already solved problems?
@GreenDimka1
@GreenDimka1 11 ай бұрын
Your problem lays in the architecture. Do not use EF in your business logic and you will not face such kind of problems. (if problem does not exist, a solution for it is not needed)
@FreddieDeetlefs
@FreddieDeetlefs 9 ай бұрын
This is pointless because if both ValueTypes take a string anyway, the mistake can still be made where a new UserId is constructed with the tenantId, and vice versa, so you just moving the issue up the pipeline, this does not solve anything
@GnomeEU
@GnomeEU 7 ай бұрын
We've NEVER had problems identifying the IDs in our projects i feel this is another over engineering that creates more problems than it solves. The more i learn about "advanced" software development the more i learn about problems that no one ever had. Why stop there, lets encapsulate every single property, then copy it at least 5 times. This solves a minor problem at best. Do these techniques just get pushed so someone can sell more books?
@md.redwanhossain6288
@md.redwanhossain6288 Ай бұрын
This is not overengineering. EF Core 8 now officially supports this feature. If this is over-engineering, microsoft will not provide the feature in the first place.
@alan-
@alan- 11 ай бұрын
The codebase keeps changing between videos. If you're following along you'll struggle unless and even with being a patreon member and having access to the source code.
@smliwhcirdeirf
@smliwhcirdeirf Жыл бұрын
For some reason I get this exception when I use the register endpoint. Mapster.CompileException: Error while compiling source=BuberDinner.Application.Authentication.Common.AuthenticationResult destination=BuberDinner.Contracts.Authentication.AuthenticationResponse type=Map ---> System.Reflection.AmbiguousMatchException: Ambiguous match found. ...
@GrimReaper160490
@GrimReaper160490 Жыл бұрын
In the mapping configs, you can enforce .ToString() on the src.MenuId.Id.Value. This makes it work as it can parse the string value to a Guid just fine.
@felixnotthecat4249
@felixnotthecat4249 10 ай бұрын
​@@GrimReaper160490 Mate, thank you so much. You made may day. I even sent Amichai an email asking for help XD Thank you again.
@tienlx97
@tienlx97 2 ай бұрын
How about 1-1 relationship : Ex: 1PurchaseOrder can belong to 1 Vendor public class PurchaseOrder : AggregateRoot { public Vendor Vendor { get; private set; } public VendorId VendorId { get; private set; } } // Vendor builder.Property(p => p.VendorId) .HasConversion( id => id.Value, value => VendorId.Create(value)); // TODO builder.HasOne(p => p.Vendor) .WithMany() .HasForeignKey(p => p.VendorId) .IsRequired(); But I can not do this
IS THIS REAL FOOD OR NOT?🤔 PIKACHU AND SONIC CONFUSE THE CAT! 😺🍫
00:41
I Built a Shelter House For myself and Сat🐱📦🏠
00:35
TooTool
Рет қаралды 30 МЛН
Backstage 🤫 tutorial #elsarca #tiktok
00:13
Elsa Arca
Рет қаралды 34 МЛН
Increíble final 😱
00:37
Juan De Dios Pantoja 2
Рет қаралды 43 МЛН
The Art of Discovering Bounded Contexts by Nick Tune
41:53
The Lesson About GUID IDs I Learned the Hard Way
15:43
Zoran Horvat
Рет қаралды 27 М.
6 INSANE Things You Didn't Know You Could Write in C#
12:26
Nick Chapsas
Рет қаралды 50 М.
Why I Don't Use Else When Programming
10:18
Web Dev Simplified
Рет қаралды 1,2 МЛН
Rust Functions Are Weird (But Be Glad)
19:52
Logan Smith
Рет қаралды 126 М.
Swagger is Going Away in .NET 9!
10:48
Nick Chapsas
Рет қаралды 84 М.
Avoid This Common Mistake in DDD Modeling
10:17
Zoran Horvat
Рет қаралды 8 М.
The New Way of Calling Your Code in .NET 8 Is INSANE
12:34
Nick Chapsas
Рет қаралды 133 М.
Clean Architecture vs Domain-Driven Design (DDD) - Understand the Difference
11:26
Iphone or nokia
0:15
rishton vines😇
Рет қаралды 1,6 МЛН
iOS 18 превратили В АНДРОИД
22:40
Overtake lab
Рет қаралды 84 М.
cool watercooled mobile phone radiator #tech #cooler #ytfeed
0:14
Stark Edition
Рет қаралды 9 МЛН