Building the Backend for a Spatial Memory Network: Lessons from the Trenches
Alright, let's talk about building a spatial memory network backend. If you've been following my journey, you know that my AR memory app idea got a serious reality check when I discovered GPS accuracy isn't what Hollywood makes it out to be. But here's the thing - the backend? That's where the real magic happens, and where I've actually learned some valuable lessons.
The Dream vs. The Reality
So, I had this grand idea: an app that lets you pin memories - photos, videos, notes - to real-world locations. You could walk down the same street years later and see all your memories come back to life. Like a digital time machine, right?
Spoiler alert: GPS doesn't work like in the movies. In dense urban areas, you're lucky to get within 20-30 meters of accuracy. But here's what I learned - the backend can actually save this concept.
Honestly, when I first started this project, I thought the main challenge would be the AR rendering. Turns out, that was just the tip of the iceberg. The backend is what makes or breaks spatial applications, and I've made enough mistakes to fill a book.
The Architecture That Actually Works
After several painful iterations, I landed on a architecture that's surprisingly simple but incredibly effective.
Core Components
@Entity
public class Memory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String description;
private String mediaUrl;
private String mediaType; // "PHOTO", "VIDEO", "NOTE"
@Column(columnDefinition = "DECIMAL(10,8)")
private Double latitude;
@Column(columnDefinition = "DECIMAL(11,8)")
private Double longitude;
@Column(columnDefinition = "DECIMAL(10,8)")
private Double accuracyRadius; // 20-30 meters in cities, 5-10 meters open areas
private Long timestamp;
private String userId;
private Map<String, Object> metadata;
@ElementCollection
@CollectionTable(name = "memory_tags")
@Column(name = "tag")
private Set<String> tags = new HashSet<>();
}
Yeah, I know what you're thinking - "That's a pretty standard entity." But here's the secret sauce: that accuracyRadius field. This is what makes the concept actually usable given real-world GPS limitations.
The Smart Location Search
The real breakthrough came when I stopped thinking about exact coordinates and started thinking about fuzzy areas.
@Service
public class MemoryService {
@Autowired
private MemoryRepository memoryRepository;
public List<Memory> findMemoriesNearby(Double latitude, Double longitude,
Double accuracyRadius, Double searchRadius) {
// Use spherical geometry to find memories within search radius
// But also check if the memory's accuracy radius overlaps with search area
String jpql = "SELECT m FROM Memory m WHERE " +
"(6371 * ACOS(COS(RADIANS(:lat)) * COS(RADIANS(m.latitude)) * " +
"COS(RADIANS(m.longitude) - RADIANS(:lng)) + " +
"SIN(RADIANS(:lat)) * SIN(RADIANS(m.latitude)))) <= :searchRadius OR " +
"(m.accuracyRadius + :searchRadius) >= " +
"(6371 * ACOS(COS(RADIANS(:lat)) * COS(RADIANS(m.latitude)) * " +
"COS(RADIANS(m.longitude) - RADIANS(:lng)) + " +
"SIN(RADIANS(:lat)) * SIN(RADIANS(m.latitude)))))";
Map<String, Object> params = new HashMap<>();
params.put("lat", latitude);
params.put("lng", longitude);
params.put("searchRadius", searchRadius);
return memoryRepository.findMemoriesByLocation(jpql, params);
}
}
So here's the thing: Instead of trying to get exact GPS matches (which is impossible in cities), this query finds memories that are either:
- Within the search radius, OR
- Have an accuracy radius that overlaps with the search area
This small change made the app actually usable in real-world conditions.
The Media Storage Nightmare (And How I Fixed It)
Let me be real with you - handling multimedia in a spatial app is a nightmare. I learned this the hard way.
Version 1: The Disaster
// Just storing files directly in the database
@Lob
private byte[] mediaData;
Yeah, that lasted about a week until I tried uploading a 4K video. The database basically screamed for mercy and died.
Version 2: The S3 Solution
@Service
public class MediaService {
@Autowired
private AmazonS3 s3Client;
private static final String BUCKET_NAME = "spatial-memory-media";
public String uploadMedia(MultipartFile file, String userId) {
try {
String fileName = userId + "/" + UUID.randomUUID() + "-" + file.getOriginalFilename();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());
s3Client.putObject(BUCKET_NAME, fileName, file.getInputStream(), metadata);
// Generate pre-signed URL for download
Date expiration = new Date(System.currentTimeMillis() + 3600000); // 1 hour
return s3Client.generatePresignedUrl(BUCKET_NAME, fileName, expiration).toString();
} catch (Exception e) {
throw new RuntimeException("Failed to upload media", e);
}
}
}
But wait, there's more! I discovered that S3 alone wasn't enough for AR applications. AR apps need fast access to media files, and the latency from S3 pre-signed URLs was killing the user experience.
Version 3: The CDN Hybrid Approach
@Configuration
public class MediaConfig {
@Bean
public MediaProcessingChain mediaProcessingChain() {
return new MediaProcessingChain()
.setStorageStrategy(new S3StorageStrategy())
.setCacheStrategy(new CDNCachingStrategy())
.setCompressionStrategy(new MediaCompressionStrategy());
}
}
public class MediaProcessingChain {
private StorageStrategy storage;
private CacheStrategy cache;
private CompressionStrategy compression;
public MediaProcessingChain process(MultipartFile file, String userId) {
// Compress first
byte[] compressed = compression.compress(file);
// Store to S3
String s3Key = storage.store(compressed, file.getOriginalFilename(), userId);
// Cache to CDN
String cdnUrl = cache.cache(s3Key);
return cdnUrl;
}
}
The lesson here? Don't just think about storing files - think about the entire delivery pipeline. AR apps need low-latency access to media, and a hybrid approach with S3 + CDN made all the difference.
Performance Optimization: The Database Wars
I went through more database schemes than I care to admit. Let me save you the pain.
Version 1: The Naive Approach
// Just let JPA handle everything
@Repository
public interface MemoryRepository extends JpaRepository<Memory, Long> {
List<Memory> findByUserId(String userId);
List<Memory> findByLatitudeBetweenAndLongitudeBetween(Double minLat, Double maxLat,
Double minLng, Double maxLng);
}
This worked fine for about 10 memories, then it all fell apart.
Version 2: The Spatial Database Attempt
// PostGIS with Spring Data
@Repository
public interface MemoryRepository extends JpaRepository<Memory, Long>, CustomMemoryRepository {
@Query(value = "SELECT * FROM memories WHERE ST_DWithin(" +
"ST_MakePoint(:lng, :lat)::geography, " +
"ST_MakePoint(longitude, latitude)::geography, " +
":radius)", nativeQuery = true)
List<Memory> findMemoriesNearby(@Param("lat") Double lat,
@Param("lng") Double lng,
@Param("radius") Double radius);
}
PostGIS is powerful, but holy crap is it complex to set up and maintain.
Version 3: The Redis + PostgreSQL Hybrid
@Service
public class SpatialCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private MemoryRepository memoryRepository;
public List<Memory> getMemoriesWithCache(Double lat, Double lng, Double radius) {
String cacheKey = String.format("memories:%s:%s:%s", lat, lng, radius);
// Try cache first
@SuppressWarnings("unchecked")
List<Memory> cached = (List<Memory>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
// Cache miss - query database
List<Memory> memories = memoryRepository.findMemoriesNearby(lat, lng, radius);
// Cache for 10 minutes
redisTemplate.opsForValue().set(cacheKey, memories, 10, TimeUnit.MINUTES);
return memories;
}
}
Honestly? The Redis cache was the game-changer. Spatial queries are expensive, and caching them for even short periods dramatically improved performance.
The User Experience That Made It Click
I learned something crucial about spatial applications - the user interface needs to work around the limitations of the technology, not pretend they don't exist.
The "Heat Map" Approach
Instead of showing exact pins on a map (which would be inaccurate), I created a heat map showing areas with high memory density.
// React component for memory heat map
const MemoryHeatMap = ({ memories, userLocation }) => {
const [heatMapData, setHeatMapData] = useState([]);
useEffect(() => {
// Calculate heat map points based on memory clusters
const clusters = clusterMemories(memories, userLocation);
setHeatMapData(clusters);
}, [memories, userLocation]);
return (
<MapContainer center={userLocation} zoom={13}>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<HeatMapLayer points={heatMapData} />
</MapContainer>
);
};
The "Memory Timeline" Feature
Users can view memories in chronological order, which is actually more useful than spatial accuracy for many cases.
const MemoryTimeline = ({ memories }) => {
const sortedMemories = useMemo(() => {
return [...memories].sort((a, b) => b.timestamp - a.timestamp);
}, [memories]);
return (
<div className="timeline">
{sortedMemories.map(memory => (
<MemoryCard key={memory.id} memory={memory} />
))}
</div>
);
};
The Hardest Lesson: When to Give Up (and When to Persist
I'll be honest - there were days when I wanted to abandon this project completely. The technical challenges seemed insurmountable, and the user testing results were... disappointing.
But here's what kept me going:
- The incremental progress - Every small victory (like getting the spatial query optimization working) kept me motivated
- The learning curve - Even if the app never becomes popular, I'm learning skills that are valuable in other contexts
- The pivot opportunity - Sometimes the limitations of one technology open doors to better solutions
I learned the hard way that passion for technology needs to be tempered with market reality. The AR spatial memory app might not be the billion-dollar idea I hoped for, but the backend architecture I built? That's actually useful in other contexts.
The Unexpected Benefits
Here's the thing I didn't expect - building this project gave me skills that have already paid dividends:
- Advanced geospatial database knowledge - I now understand spatial queries in ways I never would have otherwise
- High-performance media processing - The CDN + S3 hybrid approach I developed is actually used in production elsewhere
- Real-world GPS understanding - I now know exactly how GPS works and its limitations in practical applications
- AR/VR backend architecture - The patterns I developed for low-latency media delivery are directly applicable to AR applications
The Code That Actually Works (Mostly)
After all these iterations, here's the current production-ready code I'm most proud of:
@RestController
@RequestMapping("/api/memories")
public class MemoryController {
@Autowired
private MemoryService memoryService;
@Autowired
private MediaService mediaService;
@PostMapping
public ResponseEntity<Memory> createMemory(@RequestParam("file") MultipartFile file,
@RequestParam Double latitude,
@RequestParam Double longitude,
@RequestParam Double accuracyRadius,
@RequestParam String title,
@RequestParam(required = false) String description,
@RequestParam(required = false) Set<String> tags) {
// Upload media first
String mediaUrl = mediaService.uploadMedia(file, getCurrentUserId());
// Create memory entity
Memory memory = new Memory();
memory.setTitle(title);
memory.setDescription(description);
memory.setMediaUrl(mediaUrl);
memory.setMediaType(determineMediaType(file.getContentType()));
memory.setLatitude(latitude);
memory.setLongitude(longitude);
memory.setAccuracyRadius(accuracyRadius);
memory.setTimestamp(System.currentTimeMillis());
memory.setUserId(getCurrentUserId());
memory.setTags(tags != null ? tags : new HashSet<>());
// Save to database
Memory saved = memoryService.save(memory);
return ResponseEntity.ok(saved);
}
@GetMapping("/nearby")
public ResponseEntity<List<Memory>> getMemoriesNearby(@RequestParam Double latitude,
@RequestParam Double longitude,
@RequestParam(defaultValue = "100") Double radius) {
List<Memory> memories = memoryService.findMemoriesNearby(latitude, longitude, radius);
return ResponseEntity.ok(memories);
}
}
The Future (Maybe)
Honestly, I'm not sure if this project will ever become a successful commercial product. But the backend architecture I've built is solid, and I've learned so much that I can apply to other projects.
Here's what I'm considering:
- Open sourcing the backend - It's actually pretty good and could help other developers
- Building a spatial API as a service - The backend patterns are reusable
- Creating educational content - The lessons learned could help others avoid my mistakes
- Moving on to the next project - Sometimes you need to know when to fold your hand
What I'd Do Differently
If I could start over, here's what I'd change:
- Start with the backend first - I got too caught up in the AR frontend before the backend was solid
- Focus on a niche use case - Instead of trying to solve everyone's memory problems, I should have picked one specific scenario
- Build MVP faster - I spent too much time on features that users didn't actually need
- Get user feedback earlier - I waited too long to show the app to real users
The Bottom Line
Building a spatial memory network backend has been one of the most challenging and rewarding projects I've ever undertaken. I've made countless mistakes, but each one has taught me something valuable.
The key takeaway? Don't let the limitations of technology stop you from building cool things. Instead, work around them. The fuzzy location approach I developed might not be as precise as I originally wanted, but it's actually more practical and useful.
So if you're thinking about building a spatial application, my advice is: start with the backend, focus on the data patterns, and be prepared to pivot when reality doesn't match your dreams.
What about you? Have you ever built a spatial application? What's the biggest challenge you faced? Drop a comment below - I'd love to hear your stories and learn from your mistakes too!
Originally posted at spatial-memory - Backend API for Spatial Memory Network. If you found this helpful, consider giving the project a star on GitHub!
Top comments (0)